Shell 和命令行:Linux 的控制台
引言
如果说 Linux 内核是汽车的发动机,那么 Shell 就是方向盘和仪表盘。它是用户与操作系统交互的主要界面,是每个 Linux 用户必须掌握的核心技能。在这篇文章中,我们将系统学习 Shell 的方方面面。
什么是 Shell?
Shell 的定义
Shell 是一个命令解释器,它:
- 读取用户输入的命令
- 解释并执行命令
- 将结果返回给用户
flowchart LR
U[用户] -->|输入命令| S[Shell]
S -->|系统调用| K[内核]
K -->|执行操作| H[硬件]
H -->|返回结果| K
K -->|返回数据| S
S -->|显示输出| U
style S fill:#fff3e0
style K fill:#e3f2fd
Shell 的种类
Linux 系统中有多种 Shell:
| Shell | 全称 | 特点 |
|---|---|---|
| sh | Bourne Shell | 最初的 Unix Shell,POSIX 标准 |
| bash | Bourne Again Shell | 最常用,sh 的增强版 |
| zsh | Z Shell | 功能强大,高度可定制 |
| fish | Friendly Interactive Shell | 用户友好,自动补全强大 |
| csh/tcsh | C Shell | 语法类似 C 语言 |
# 查看系统支持的 Shell
cat /etc/shells
# 查看当前使用的 Shell
echo $SHELL
# 查看 Shell 版本
bash --version
为什么选择 Bash?
- ✅ 默认安装:几乎所有 Linux 发行版默认 Shell
- ✅ 兼容性好:符合 POSIX 标准
- ✅ 功能强大:命令历史、自动补全、脚本编程
- ✅ 社区支持:大量教程和脚本资源
Bash 基础
命令的基本结构
command [-options] [arguments]
# 命令 选项 参数
示例:
ls -l /home/user
# ↑ ↑ └─ 参数:操作对象
# │ └────── 选项:改变命令行为
# └─────────── 命令:要执行的程序
常用命令速查
文件和目录操作
# 列出文件
ls # 简单列出
ls -la # 详细列表,包括隐藏文件
ls -lh # 人类可读的文件大小
# 目录切换
cd /home/user # 绝对路径
cd Documents # 相对路径
cd ~ # 回到家目录
cd .. # 返回上级目录
cd - # 返回上一个目录
# 显示当前目录
pwd
# 创建目录
mkdir dir_name # 创建单个目录
mkdir -p /a/b/c # 递归创建多级目录
mkdir -p dir1 dir2 dir3 # 同时创建多个目录
# 删除目录
rmdir empty_dir # 删除空目录
rm -r dir_name # 递归删除目录及内容
rm -rf dir_name # 强制删除,不询问
文件操作
# 创建文件
touch file.txt # 创建空文件
touch file1.txt file2.txt # 同时创建多个文件
# 复制文件
cp file1.txt file2.txt # 复制并重命名
cp -r dir1 dir2 # 递归复制目录
cp -p file1.txt file2.txt # 保留属性
# 移动/重命名
mv old_name new_name # 重命名
mv file.txt /tmp/ # 移动文件
# 删除文件
rm file.txt # 删除文件
rm -i file.txt # 删除前确认
rm -f file.txt # 强制删除
# 查看文件内容
cat file.txt # 显示全部内容
less file.txt # 分页查看(推荐)
head -n 10 file.txt # 查看前10行
tail -n 10 file.txt # 查看后10行
tail -f /var/log/syslog # 实时查看日志
# 编辑文件
nano file.txt # 简单编辑器
vim file.txt # 强大的编辑器
文件查找
# find:按条件查找文件
find /home -name "*.txt" # 按名称查找
find /home -type f -name "*.conf" # 查找文件
find /home -type d -name "log" # 查找目录
find /home -size +100M # 查找大于100MB的文件
find /home -mtime -7 # 查找7天内修改的文件
# locate:快速查找(使用数据库)
locate nginx.conf # 查找文件
sudo updatedb # 更新数据库
# which:查找命令位置
which python3
which ls
# grep:搜索文件内容
grep "error" /var/log/syslog # 搜索包含error的行
grep -r "TODO" ./ # 递归搜索目录
grep -i "hello" file.txt # 忽略大小写
grep -n "pattern" file.txt # 显示行号
grep -v "comment" file.txt # 反向匹配
环境变量
什么是环境变量
环境变量是存储在 Shell 中的键值对,用于控制系统行为和存储配置信息。
常见环境变量
$HOME # 当前用户家目录
$USER # 当前用户名
$PATH # 命令搜索路径
$PWD # 当前工作目录
$SHELL # 当前使用的 Shell
$HOSTNAME # 主机名
$LANG # 语言和本地化设置
$DISPLAY # X 显示服务器
$EDITOR # 默认编辑器
环境变量操作
# 查看所有环境变量
env
printenv
export
# 查看特定变量
echo $PATH
echo $HOME
# 设置临时变量(当前 Shell 会话有效)
MY_VAR="hello"
export MY_VAR # 导出为环境变量
# 设置永久变量(写入配置文件)
echo 'export MY_VAR="hello"' >> ~/.bashrc
source ~/.bashrc # 重新加载配置
# 删除变量
unset MY_VAR
PATH 变量详解
# 查看 PATH
echo $PATH
# 输出:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# 添加新路径到 PATH(临时)
export PATH=$PATH:/opt/myapp/bin
# 添加新路径到 PATH(永久)
echo 'export PATH=$PATH:/opt/myapp/bin' >> ~/.bashrc
# 命令查找顺序
# 1. 别名(alias)
# 2. 内置命令(builtin)
# 3. PATH 中的可执行文件
修改 PATH 时注意安全,不要在前面添加不可信的路径,以免被恶意程序替换系统命令。
管道和重定向
标准流
每个进程默认打开三个流:
flowchart LR
F[文件] -->|0 stdin| P[进程]
P -->|1 stdout| T[终端]
P -->|2 stderr| T
style F fill:#e3f2fd
style P fill:#fff3e0
style T fill:#f3e5f5
- stdin(0):标准输入,默认来自键盘
- stdout(1):标准输出,默认输出到终端
- stderr(2):标准错误,默认输出到终端
输出重定向
# 覆盖重定向(>)
echo "Hello" > file.txt # 覆盖文件内容
# 追加重定向(>>)
echo "World" >> file.txt # 追加到文件末尾
# 重定向标准错误(2>)
command 2> error.log # 错误输出到文件
command 2>> error.log # 追加错误
# 同时重定向
command > output.log 2>&1 # 标准输出和错误都重定向
command &> log.txt # 同上(Bash 简写)
command > /dev/null 2>&1 # 丢弃所有输出
# 只保留错误
command 2>&1 > /dev/null # 只显示错误
输入重定向
# 从文件读取输入
mysql -u root -p < backup.sql
# Here Document
cat << EOF
Hello
This is a
multi-line text
EOF
# Here String(Bash 特有)
grep "pattern" <<< "Hello World"
管道
管道将一个命令的输出作为另一个命令的输入:
command1 | command2 | command3
# 示例
ps aux | grep nginx | grep -v grep
cat /var/log/syslog | grep "error" | tail -20
# 常用管道组合
ps aux | sort -k4 -nr | head -10 # CPU 使用率最高的进程
du -h /home | sort -hr | head -10 # 占用空间最大的目录
history | awk '{print $2}' | sort | uniq -c | sort -nr | head
flowchart LR
A[命令1] -->|stdout| B[管道]
B -->|stdin| C[命令2]
C -->|stdout| D[命令3]
D -->|stdout| T[终端]
style B fill:#fff3e0
命令别名和函数
别名(alias)
# 创建别名
alias ll='ls -laF'
alias grep='grep --color=auto'
alias c='clear'
# 查看所有别名
alias
# 删除别名
unalias ll
# 永久保存(写入 ~/.bashrc)
echo "alias ll='ls -laF'" >> ~/.bashrc
常用别名推荐
# 在 ~/.bashrc 中添加
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
alias ..='cd ..'
alias ...='cd ../..'
alias grep='grep --color=auto'
alias fgrep='fgrep --color=auto'
alias egrep='egrep --color=auto'
alias df='df -h'
alias du='du -h'
alias free='free -h'
alias tree='tree -C'
alias mkdir='mkdir -pv'
alias wget='wget -c'
alias histg='history | grep'
alias myip='curl http://ipecho.net/plain; echo'
函数
# 定义函数
function mkcd() {
mkdir -p "$1" && cd "$1"
}
# 或简化写法
mkcd() {
mkdir -p "$1" && cd "$1"
}
# 使用函数
mkcd newproject
# 查看函数定义
declare -f mkcd
# 删除函数
unset -f mkcd
实用函数示例
# 提取压缩文件
extract() {
if [ -f $1 ]; then
case $1 in
*.tar.bz2) tar xjf $1 ;;
*.tar.gz) tar xzf $1 ;;
*.bz2) bunzip2 $1 ;;
*.rar) unrar x $1 ;;
*.gz) gunzip $1 ;;
*.tar) tar xf $1 ;;
*.tbz2) tar xjf $1 ;;
*.tgz) tar xzf $1 ;;
*.zip) unzip $1 ;;
*.Z) uncompress $1;;
*.7z) 7z x $1 ;;
*) echo "'$1' cannot be extracted via extract()" ;;
esac
else
echo "'$1' is not a valid file"
fi
}
# 创建备份
backup() {
tar -czvf "backup_$(date +%Y%m%d_%H%M%S).tar.gz" "$@"
}
# 查找并杀死进程
killport() {
lsof -ti:$1 | xargs kill -9
}
# 快速搜索文件内容
search() {
grep -r "$1" . --include="*.py" --include="*.js" --include="*.html"
}
Bash 脚本编程
脚本基础
第一个脚本
#!/bin/bash
# Shebang: 指定解释器
# 这是一个注释
echo "Hello, World!"
# 赋予执行权限
chmod +x script.sh
# 执行脚本
./script.sh
# 或使用 bash 解释
bash script.sh
变量
#!/bin/bash
# 变量定义(注意:等号两边不能有空格)
name="Zhang San"
age=25
# 变量使用
echo "My name is $name"
echo "I am $age years old"
# 只读变量
readonly PI=3.14159
# 删除变量
unset age
# 特殊变量
echo "Script name: $0" # 脚本名称
echo "First arg: $1" # 第一个参数
echo "Second arg: $2" # 第二个参数
echo "All args: $@" # 所有参数
echo "Number of args: $#" # 参数个数
echo "Process ID: $$" # 当前进程PID
echo "Exit status: $?" # 上一条命令的退出状态
数组
#!/bin/bash
# 定义数组
fruits=("Apple" "Banana" "Orange")
# 访问数组元素
echo ${fruits[0]} # 第一个元素
echo ${fruits[@]} # 所有元素
echo ${#fruits[@]} # 数组长度
echo ${!fruits[@]} # 所有索引
# 添加元素
fruits+=("Grape")
# 遍历数组
for fruit in "${fruits[@]}"; do
echo "I like $fruit"
done
条件判断
#!/bin/bash
# if 语句
age=18
if [ $age -ge 18 ]; then
echo "You are an adult"
elif [ $age -ge 13 ]; then
echo "You are a teenager"
else
echo "You are a child"
fi
# 文件测试
filename="test.txt"
if [ -f "$filename" ]; then
echo "File exists"
elif [ -d "$filename" ]; then
echo "Directory exists"
else
echo "Not found"
fi
# 字符串比较
str1="hello"
str2="world"
if [ "$str1" = "$str2" ]; then
echo "Strings are equal"
elif [ "$str1" != "$str2" ]; then
echo "Strings are different"
fi
# 逻辑运算
if [ $age -ge 18 ] && [ $age -le 65 ]; then
echo "Working age"
fi
常用测试操作符:
# 文件测试
-f file # 文件存在且是普通文件
-d dir # 目录存在
-r file # 文件可读
-w file # 文件可写
-x file # 文件可执行
-s file # 文件大小非零
# 字符串测试
-z str # 字符串为空
-n str # 字符串非空
str1 = str2 # 字符串相等
str1 != str2 # 字符串不等
# 数值比较
num1 -eq num2 # 等于
num1 -ne num2 # 不等于
num1 -gt num2 # 大于
num1 -lt num2 # 小于
num1 -ge num2 # 大于等于
num1 -le num2 # 小于等于
循环
#!/bin/bash
# for 循环
for i in {1..10}; do
echo "Number: $i"
done
# 遍历文件
for file in *.txt; do
echo "Processing: $file"
done
# C 风格 for 循环
for ((i=0; i<10; i++)); do
echo $i
done
# while 循环
count=0
while [ $count -lt 10 ]; do
echo "Count: $count"
((count++))
done
# 读取文件行
while IFS= read -r line; do
echo "$line"
done < input.txt
# until 循环
count=0
until [ $count -ge 10 ]; do
echo "Count: $count"
((count++))
done
函数
#!/bin/bash
# 定义函数
greet() {
local name=$1
echo "Hello, $name!"
}
# 调用函数
greet "Zhang San"
# 返回值
add() {
local num1=$1
local num2=$2
return $((num1 + num2))
}
add 5 3
echo "Sum: $?" # 获取返回值
# 返回字符串(使用 echo)
get_info() {
echo "Name: $1"
echo "Age: $2"
}
result=$(get_info "Zhang San" 25)
echo "$result"
实战示例
系统监控脚本
#!/bin/bash
# system_monitor.sh - 简单系统监控脚本
echo "=== System Monitor ==="
echo "Time: $(date)"
echo ""
# CPU 使用率
echo "--- CPU Usage ---"
top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" \
| awk '{print "CPU Usage: " 100 - $1"%"}'
# 内存使用
echo ""
echo "--- Memory Usage ---"
free -h | awk 'NR==2{printf "Total: %s\nUsed: %s\nFree: %s\nUsage: %.2f%%\n",
$2,$3,$4,$3*100/($3+$4)}'
# 磁盘使用
echo ""
echo "--- Disk Usage ---"
df -h | awk 'NR>1 && $5+0 > 80 {printf "⚠️ %s is %s full\n",$5,$6}'
# 前5个进程
echo ""
echo "--- Top 5 Processes ---"
ps aux --sort=-%cpu | head -6 | awk '{printf "%-10s %-10s %-10s %s\n",$1,$2,$3,$11}'
日志备份脚本
#!/bin/bash
# backup_logs.sh - 备份并压缩日志文件
LOG_DIR="/var/log"
BACKUP_DIR="/backup/logs"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="logs_backup_${DATE}.tar.gz"
# 检查并创建备份目录
[ ! -d "$BACKUP_DIR" ] && mkdir -p "$BACKUP_DIR"
# 查找7天内的日志文件并备份
find "$LOG_DIR" -name "*.log" -mtime -7 -print0 | \
tar -czvf "${BACKUP_DIR}/${BACKUP_FILE}" --null -T -
if [ $? -eq 0 ]; then
echo "✅ Backup successful: ${BACKUP_DIR}/${BACKUP_FILE}"
# 只保留最近30天的备份
find "$BACKUP_DIR" -name "logs_backup_*.tar.gz" -mtime +30 -delete
echo "🗑️ Old backups cleaned up"
else
echo "❌ Backup failed!"
exit 1
fi
命令行技巧
命令历史
# 查看历史
history
# 执行历史中的第 n 条命令
!100
# 执行上一条命令
!!
# 执行上一条命令的参数
!$: # 上一个命令的最后一个参数
!*: # 上一个命令的所有参数
# 搜索历史(Ctrl+R 反向搜索)
# (type) Ctrl+R
# (reverse-i-search)`ssh': ssh user@192.168.1.100
# 清空历史
history -c
# 设置历史大小
echo 'HISTSIZE=10000' >> ~/.bashrc
echo 'HISTFILESIZE=20000' >> ~/.bashrc
命令补全
# Tab 键补全
# 1. 命令补全
# (type) git <Tab>
# 显示:git git-receive-pack git-shell git-upload-pack git-upload-archive
# 2. 路径补全
# (type) cd /ho<Tab>
# 自动补全为 cd /home/
# 3. 参数补全
# (type) systemctl s<Tab><Tab>
# 显示可能的参数:start stop status restart
# Bash 补全配置
# 安装 bash-completion
sudo apt install bash-completion
# 启用补全(在 ~/.bashrc 中)
if ! shopt -oq posix; then
source /etc/bash_completion
fi
快捷键
| 快捷键 | 功能 |
|---|---|
| Ctrl+C | 中断当前命令 |
| Ctrl+D | 退出 Shell |
| Ctrl+Z | 暂停当前命令(fg 恢复) |
| Ctrl+L | 清屏 |
| Ctrl+A | 移到命令行首 |
| Ctrl+E | 移到命令行尾 |
| Ctrl+U | 删除到行首 |
| Ctrl+K | 删除到行尾 |
| Ctrl+W | 删除前一个单词 |
| Ctrl+Y | 粘贴删除的内容 |
| Ctrl+R | 搜索历史命令 |
| !! | 执行上一条命令 |
| !$ | 上一条命令的最后一个参数 |
| Esc+. | 上一条命令的最后一个参数(另一种方式) |
作业控制
# 前台和后台
command & # 后台运行
Ctrl+Z # 暂停前台命令
bg # 将暂停的命令放到后台
fg # 将后台命令提到前台
# 查看作业
jobs
# 结束作业
kill %1 # 结束作业号1
Screen/Tmux 会话管理
详见 Tmux 博客文章,这里简要介绍:
# Tmux
tmux new -s session_name # 创建会话
tmux ls # 列出会话
tmux attach -t session_name # 连接会话
Ctrl+b d # 分离会话
# Screen
screen -S session_name # 创建会话
screen -ls # 列出会话
screen -r session_name # 连接会话
Ctrl+a d # 分离会话
最佳实践
1. 代码风格
#!/bin/bash
# 脚本描述
# 作者:Your Name
# 日期:YYYY-MM-DD
set -euo pipefail # 严格模式
# 使用变量而非硬编码
LOG_DIR="/var/log"
CONFIG_FILE="/etc/myapp/config.conf"
# 使用引号包裹变量
if [ -f "$CONFIG_FILE" ]; then
source "$CONFIG_FILE"
fi
# 注释复杂逻辑
# 计算平均值
average=$((total / count))
# 函数命名使用动词
check_status() { :; }
start_service() { :; }
stop_service() { :; }
2. 错误处理
#!/bin/bash
# 严格模式
set -e # 遇到错误立即退出
set -u # 使用未定义变量时报错
set -o pipefail # 管道中任何命令失败都返回错误
# 或组合使用
set -euo pipefail
# 捕获退出信号
trap 'echo "Script interrupted"; exit 1' INT
# 检查命令是否存在
command -v docker >/dev/null 2>&1 || {
echo "Docker is not installed!"
exit 1
}
# 检查文件是否存在
[ -f "$CONFIG_FILE" ] || {
echo "Config file not found!"
exit 1
}
3. 日志记录
#!/bin/bash
# 日志函数
log() {
local level=$1
shift
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" | tee -a app.log
}
log_info() { log "INFO" "$@"; }
log_warn() { log "WARN" "$@"; }
log_error() { log "ERROR" "$@"; }
# 使用
log_info "Starting application..."
log_warn "Configuration file not found, using defaults"
log_error "Failed to connect to database"
4. 参数解析
#!/bin/bash
# 显示帮助信息
usage() {
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " -h, --help Show this help message"
echo " -v, --verbose Verbose mode"
echo " -f FILE Input file"
exit 0
}
# 解析参数
VERBOSE=false
INPUT_FILE=""
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
usage
;;
-v|--verbose)
VERBOSE=true
shift
;;
-f|--file)
INPUT_FILE="$2"
shift 2
;;
*)
echo "Unknown option: $1"
usage
;;
esac
done
# 使用参数
if [ "$VERBOSE" = true ]; then
echo "Verbose mode enabled"
fi
if [ -n "$INPUT_FILE" ]; then
echo "Processing file: $INPUT_FILE"
fi
总结
Shell 和命令行是 Linux 世界的瑞士军刀。掌握 Shell 让你能够:
核心能力
- 高效操作:命令行比 GUI 快得多
- 自动化:脚本自动化重复任务
- 系统管理:深入控制和监控
- 远程工作:SSH 管理远程服务器
学习路径
flowchart LR
A[基础命令] --> B[管道重定向]
B --> C[脚本编程]
C --> D[高级技巧]
D --> E[效率提升]
style A fill:#e8f5e9
style B fill:#c8e6c9
style C fill:#a5d6a7
style D fill:#81c784
style E fill:#4caf50
实践建议
- 每日使用:让 Shell 成为日常工作的一部分
- 阅读代码:学习开源项目的 Shell 脚本
- 编写脚本:自动化日常任务
- 探索工具:发现新的命令和技巧
- 参考文档:
man和--help是好朋友
“The shell is the way power users talk to the operating system.” — Unix Philosophy
在下一篇文章中,我们将深入学习 进程管理,了解 Linux 如何管理和调度进程。
← 返回系列概述: Linux 概述:从 Unix 到开源革命
参考资源: