Featured image of post Shell 和命令行:Linux 的控制台

Shell 和命令行:Linux 的控制台

掌握 Linux Shell 基础、环境变量、管道重定向、Bash 脚本编程以及高效使用命令行的技巧

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 让你能够:

核心能力

  1. 高效操作:命令行比 GUI 快得多
  2. 自动化:脚本自动化重复任务
  3. 系统管理:深入控制和监控
  4. 远程工作: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

实践建议

  1. 每日使用:让 Shell 成为日常工作的一部分
  2. 阅读代码:学习开源项目的 Shell 脚本
  3. 编写脚本:自动化日常任务
  4. 探索工具:发现新的命令和技巧
  5. 参考文档man--help 是好朋友

“The shell is the way power users talk to the operating system.” — Unix Philosophy

在下一篇文章中,我们将深入学习 进程管理,了解 Linux 如何管理和调度进程。


← 返回系列概述: Linux 概述:从 Unix 到开源革命


参考资源

A winner is just a loser who tried one more time.
Robust AI
使用 Hugo 构建
主题 StackJimmy 设计