Jacky's blog
首页
  • Android
  • Web
  • Server
  • Python
  • iOS
  • Java
  • Vue
  • 计算机基础
  • AI
  • 全部工具
  • CLI 工具
  • 编辑器
  • 开发工具
  • 构建工具
  • 版本控制
  • 调试工具
  • 网络工具
  • 个人成长
  • 博客
  • 分类
  • 标签
  • 归档
收藏
GitHub (opens new window)

Jack Yang

编程; 随笔
首页
  • Android
  • Web
  • Server
  • Python
  • iOS
  • Java
  • Vue
  • 计算机基础
  • AI
  • 全部工具
  • CLI 工具
  • 编辑器
  • 开发工具
  • 构建工具
  • 版本控制
  • 调试工具
  • 网络工具
  • 个人成长
  • 博客
  • 分类
  • 标签
  • 归档
收藏
GitHub (opens new window)
  • 计算机基础

  • 网络

  • 操作系统

  • Shell

    • shell 入门指南
      • Shell 简介
        • 什么是 Shell
        • 常见 Shell 种类
        • POSIX 是什么
        • GNU 是什么
        • Shebang(#!/)
        • 脚本头部模板
      • 变量与参数
        • 特殊变量
        • 变量默认值
        • 字符串操作
        • 数组
        • 关联数组(bash 4+)
      • 执行模型
        • 父子 Shell
        • 进程替换
        • Here Document
        • 重定向
        • IFS 与逐行读取
      • 函数与流程
        • 函数
        • test 命令
        • 参数验证
      • 错误处理与健壮性
        • 严格模式
        • 错误捕获与降级
        • 信号与清理
        • 作业控制
      • 实用片段
        • PATH 修改
        • 常用小命令
        • 交互配置
        • 输出着色
        • 调试
      • 性能与最佳实践
        • 性能要点
        • 变量引用
        • 临时文件
      • 常用工具函数
      • link
    • Shell 常用命令速查手册
    • Shell 代码片段集合
  • 安全

  • 设计模式

  • cs
  • shell
Jacky
2023-06-04
目录

shell 入门指南

Shell 基础速查:Shell 种类与区别 → 变量与参数 → 执行模型 → 函数与流程 → 错误处理 → 实用片段 → 性能与最佳实践。

# Shell 简介

# 什么是 Shell

Shell 是命令行解释器,负责接收用户输入的命令并调用操作系统执行。它既是交互式的命令行环境,也是脚本语言的解释器。

# 常见 Shell 种类

Shell 全称 特点 适用场景
sh Bourne Shell 最原始的 shell,POSIX 标准 跨平台脚本,最小兼容
bash Bourne Again Shell GNU 项目的 shell,sh 的增强版 Linux 默认,功能丰富
zsh Z Shell bash 的增强版,补全更强 macOS 默认,交互式体验好
fish Friendly Interactive Shell 开箱即用,语法不同 新手友好,交互式
dash Debian Almquist Shell 轻量 POSIX shell Ubuntu 的 /bin/sh,启动快

# POSIX 是什么

POSIX(Portable Operating System Interface)是一组操作系统接口标准。

  • POSIX shell 是 shell 语言的标准子集
  • 符合 POSIX 的脚本可以在任何 POSIX 系统上运行
  • 常见的 POSIX shell:sh、dash、bash --posix、zsh --emulate sh

# GNU 是什么

GNU 是一个自由软件项目,目标是创建一套完全自由的类 Unix 操作系统。

  • bash 是 GNU 项目的产物
  • coreutils(ls、cp、mv 等)也是 GNU 项目的
  • GNU 工具通常比 BSD 版本功能更多,但兼容性稍差

GNU 与 BSD 工具差异的典型实战见 [sed 用法与 BSD/GNU 兼容性踩坑](../../07.tools/07.cli_tools/21.sed.md#BSD sed vs GNU sed 兼容性):同一脚本本地能跑、Xcode 里报错的全过程。

# Shebang(#!/)

脚本第一行的特殊注释,告诉系统用什么解释器来跑这个脚本。

Shebang 说明 兼容性
#!/bin/sh 用 POSIX shell 跑 最好,跨平台
#!/bin/bash 用 bash 跑 Linux 默认,macOS 也有
#!/usr/bin/env bash 自动找 bash 路径 更灵活,推荐
#!/usr/bin/env python3 用 python3 跑 Python 脚本

选择建议:

  • 写跨平台脚本 → #!/bin/sh
  • 用 bash 特有功能 → #!/usr/bin/env bash
  • 用 env 更灵活,不用硬编码路径

# 脚本头部模板

#!/usr/bin/env bash
set -euo pipefail    # 严格模式(-e 出错即退、-u 未定义变量报错、-o pipefail 管道任一失败即失败)
IFS=$'\n\t'          # 收紧分词边界,减少空格误展开
1
2
3

# 变量与参数

# 特殊变量

变量 含义
$0 脚本名(按调用方式可能是相对路径、绝对路径或文件名)
$1 $2 … 位置参数
$# 参数个数
$@ 全部参数;加引号 "$@" 时每个参数独立成词
$* 全部参数;加引号 "$*" 时合并为一个字符串(以 IFS 首字符连接)
$? 上一命令退出码(0 成功,非 0 失败)
$$ 当前 shell 的 PID
$! 最近一个后台进程的 PID
$_ 上一命令的最后一个参数
$PWD / $OLDPWD 当前 / 上一个工作目录
$HOME $USER $SHELL 用户环境
$HOSTNAME 主机名(bash 自设但不保证导出,脚本中用 hostname 命令更稳)
$RANDOM 0~32767 随机数(bash/zsh)
$SECONDS shell 已运行秒数(bash/zsh)
$LINENO 当前行号
$FUNCNAME 函数名数组([0] 为当前函数,构成调用栈)(bash)
$BASH_SOURCE 源文件路径数组(被 source 时与 $0 不同)(bash)

标注(bash)的变量是 bash 特有的,POSIX sh 里没有。

$* 与 $@ 的区别只在加双引号时体现:

set -- "a b" c
printf '[%s]' "$*"    # [a b c]      —— 一个词
printf '[%s]' "$@"    # [a b][c]     —— 两个词
1
2
3

遍历参数永远用 "$@"。

$0 的常见取值:

  • ./script.sh → $0 = ./script.sh;/path/to/script.sh → $0 = /path/to/script.sh
  • 纯文件名:basename "$0" 或 ${0##*/}
  • 完整路径:realpath "$0" 或 readlink -f "$0"(Linux)

$_ 实用技巧:

mkdir -p /path/to/dir && cd $_    # 建目录后直接切进去
1

# 变量默认值

语法 含义
${var:-default} var 未定义或为空时用 default(不赋值)
${var-default} 仅 var 未定义时用 default(为空不用)
${var:=default} 未定义或为空时赋值为 default
${var:?message} 未定义或为空时报错退出
${var:+alternative} 已定义且非空时用 alternative(否则为空)
export ZSH_CUSTOM="${ZSH_CUSTOM:-~/.oh-my-zsh/custom}"    # 未定义则用默认值
remote_name="${1:-origin}"                                # 无第一个参数则用 origin
1
2

# 字符串操作

语法 含义
${#string} 长度
${string:position} ${string:position:length} 从 position 起取子串
${string#pattern} / ${string##pattern} 从头删除最短 / 最长匹配前缀
${string%pattern} / ${string%%pattern} 从尾删除最短 / 最长匹配后缀
${string/pat/rep} / ${string//pat/rep} 替换第一个 / 全部匹配
${string/#pat/rep} / ${string/%pat/rep} 仅在前缀处 / 后缀处替换
script="${0##*/}"              # 纯文件名:从左删最长 "*/" 前缀
name="${file%.*}"              # 去扩展名:从右删最短 ".*" 后缀
ext="${file##*.}"              # 只取扩展名
out="${file%.*}_compressed.jpg"
lower="${var,,}"  upper="${var^^}"    # 转小写 / 大写(bash 4+)
1
2
3
4
5

# 数组

arr=("apple" "banana" "cherry")

echo "${arr[0]}"     # 第一个元素
echo "${arr[@]}"     # 所有元素
echo "${#arr[@]}"    # 数组长度
arr+=("date")        # 追加元素

for item in "${arr[@]}"; do
    echo "$item"
done
1
2
3
4
5
6
7
8
9
10

数组是 bash 特有的,POSIX sh 里没有。

# 关联数组(bash 4+)

declare -A colors
colors["red"]="#FF0000"
colors["green"]="#00FF00"

echo "${colors["red"]}"

for key in "${!colors[@]}"; do    # ${!arr[@]} 取全部键
    echo "$key: ${colors[$key]}"
done
1
2
3
4
5
6
7
8
9

# 执行模型

# 父子 Shell

  • 普通命令在当前 shell 中执行,变量与 cd 直接生效
  • (...) 创建子 shell:内部的 cd、变量定义只在子 shell 内生效,不影响父 shell。适合临时切换环境
  • 管道的每一段命令也各自运行在子 shell 中——管道内修改变量、cd,结束后全部丢失
  • 进程替换 <(...) 同样是子 shell

经典陷阱:管道接 while 循环,循环内的计数在子 shell 里,循环外拿不到:

# ✗ count 在子 shell 中自增,循环结束后丢失
cat file.txt | while IFS= read -r line; do ((count++)); done
echo "$count"    # 空

# ✓ 用重定向代替管道,循环在当前 shell 执行
count=0
while IFS= read -r line; do ((count++)); done < file.txt
echo "$count"
1
2
3
4
5
6
7
8

# 进程替换

<(...) 把命令输出包装成临时文件,供只接受文件参数的命令使用(bash/zsh):

diff <(ls /path1) <(ls /path2)                       # 比较两个命令的输出
cat <(echo "Hello World")
paste <(cut -d: -f1 /etc/passwd) <(cut -d: -f3 /etc/passwd)
1
2
3

# Here Document

<<TAG 把多行文本作为命令输入,TAG 为自定义结束标记(文本中未出现即可):

cat << END
这是一段
多行文本
END
1
2
3
4

关键区别:tag 加不加引号决定是否做变量展开——

cat << EOF            # 展开 $var
$(date) $USER
EOF

cat << 'EOF'          # 不展开,原样输出
$(date) $USER
EOF
1
2
3
4
5
6
7

写文件与赋值多行字符串:

cat << END > config.pbtx    # 多行文本写入文件
这是一段配置
END

info=$(cat << 'EOF'         # 多行字符串赋值给变量(引号防展开)
line1
line2
EOF
)
echo "$info"
1
2
3
4
5
6
7
8
9
10

# 重定向

写法 含义
cmd > file / cmd >> file stdout 覆盖 / 追加到文件
cmd 2> file / cmd 2>> file stderr 重定向
cmd > file 2>&1 stdout 和 stderr 都进 file(bash 可简写 &>file)
cmd > /dev/null 2>&1 丢弃全部输出
cmd 2>&1 | less 合并后进管道

2>&1 拆解:2 是 stderr 的文件描述符,> 重定向,&1 表示"引用 fd 1(stdout)",即把 stderr 合并到 stdout 当前指向的位置。

顺序很重要:cmd 2>&1 > file 先把 stderr 指向当时的 stdout(终端),再把 stdout 指向 file——stderr 仍打到终端。要合并进文件必须 > file 2>&1。

# IFS 与逐行读取

IFS(Internal Field Separator)决定 shell 分词的边界。逐行读取的标准写法:

while IFS= read -r path; do
    [ -z "$path" ] && continue
    ...
done < "$input_file"
1
2
3
4
  • IFS=(置空):不裁剪行首尾空白,整行作为单个变量值
  • -r:反斜杠不做转义,路径等特殊字符原样保留

按分隔符拆分(如空格):

while IFS=' ' read -r word; do
    echo "$word"
done <<< "abc ddd eee fff"
1
2
3

# 函数与流程

# 函数

function_name() {
    local var1="$1" var2="$2"    # local 限制作用域;参数用位置变量
    echo "参数1: $var1, 参数2: $var2"
    return 0                     # 退出码 0~255,不是返回值
}

function_name "hello" "world"
echo "退出码: $?"
1
2
3
4
5
6
7
8

函数"返回值"有两种,不要混淆:

  • return N / $?:退出码(0~255),表示成功失败
  • 返回数据:echo 输出 + 命令替换捕获
get_dir() {
    echo "/data/local/tmp"    # 数据用 echo
}
dir=$(get_dir)                # 捕获
1
2
3
4
process_files() {
    local count=0
    for file in "$@"; do
        [ -f "$file" ] && ((count++))
    done
    echo "共处理 $count 个文件"
}
1
2
3
4
5
6
7

# test 命令

test command reference (opens new window)

类别 操作符
数值 -eq -ne -gt -ge -lt -le
字符串 -z(空) -n(非空) = !=
文件 -e(存在) -f(普通文件) -d(目录) -r -w -x -s(非空) -c -b
[ -f "$file" ]       # 文件存在
[ -d "$dir" ]        # 目录存在
[ -n "$var" ]        # 字符串非空
[ "$num1" -gt "$num2" ]
1
2
3
4

bash 中推荐内建 [[ ]]:不做词分割、变量可不加引号、支持 &&/||/=~ 正则(bash/zsh):

if [[ -f "$file" && "$name" =~ ^v[0-9]+$ ]]; then ...; fi
1

# 参数验证

# 参数数量
[ $# -lt 2 ] && { echo "用法: $0 <参数1> <参数2>" >&2; exit 1; }

# 文件存在
[ ! -f "$1" ] && { echo "错误: 文件 '$1' 不存在" >&2; exit 1; }

# 目录可写
[ ! -w "$(dirname "$output")" ] && { echo "错误: 无法写入目录" >&2; exit 1; }
1
2
3
4
5
6
7
8

# 错误处理与健壮性

# 严格模式

set -e             # 命令失败(非0退出码)立即退出
set -u             # 引用未定义变量报错
set -o pipefail    # 管道中任一段失败则整条管道失败
1
2
3

# 错误捕获与降级

if ! command_that_might_fail; then
    echo "命令执行失败" >&2
    exit 1
fi

error_output=$(command 2>&1)              # 捕获 stdout+stderr
result=$(command 2>/dev/null) || result="default"    # 忽略错误并降级
command 2>> error.log                     # 错误追加到日志
1
2
3
4
5
6
7
8

# 信号与清理

temp_file=$(mktemp)
trap 'rm -f "$temp_file"' EXIT    # 单引号:退出时才展开变量

cleanup() {
    echo "收到退出信号,清理中..."
    rm -f /tmp/temp_file
    exit 0
}
trap cleanup SIGINT SIGTERM
1
2
3
4
5
6
7
8
9

# 作业控制

command &        # 后台运行
jobs -l          # 列出后台任务
fg %1            # 移到前台
bg %1            # 前台任务转后台继续
kill %1          # 杀死任务
1
2
3
4
5

# 实用片段

# PATH 修改

# 追加到末尾(低优先级);变量未定义时会拼出 "/bin",注意先确认变量存在
export PATH="$PATH:$ANDROID_BUILD_TOOL/bin"

# 前插到开头(高优先级,优先命中)
export PATH="/usr/local/opt/postgresql@16/bin:$PATH"
1
2
3
4
5

# 常用小命令

printf "%x\n" 102        # 十进制转 16 进制 → 66
printf "%d\n" 0x66       # 16 进制转十进制 → 102
mktemp                   # 创建临时文件
basename /a/b/c.txt      # → c.txt(取文件名)
dirname /a/b/c.txt       # → /a/b(取目录名)
readlink -f file         # 符号链接的真实路径
type cmd                  # 显示命令类型(别名/函数/内建/文件)
command -v cmd            # 查找命令路径(POSIX 标准,脚本判断命令存在的首选)
1
2
3
4
5
6
7
8

# 交互配置

  • set -o vi:bash 切到 vi 编辑模式,光标移动同 vi

# 输出着色

color_echo() {
    echo -e "\033[${1}m${2}\033[0m"
}
color_echo "0;31" "红色文本"    # 0;31 红色,0;32 绿色,0;33 黄色
1
2
3
4

# 调试

set -x    # 打印执行的每条命令
set +x    # 关闭(脚本内局部调试)
bash -x script.sh              # 从外部启动调试
trap 'echo "错误发生在第 $LINENO 行" >&2' ERR
1
2
3
4
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >&2
}
1
2
3

# 性能与最佳实践

# 性能要点

# 1. 拒绝无用 cat
grep "pattern" file.txt            # ✓ 直接读文件
cat file.txt | grep "pattern"      # ✗ 多余一次 fork

# 2. 循环内避免调用外部命令——用参数扩展替代
for f in *.txt; do
    base="${f%.*}"                 # ✓ 内置,零开销
    base=$(basename "$f" .txt)     # ✗ 每轮 fork 一个进程
done

# 3. 统计类操作交给专用命令,一次调用远快于 shell 循环
count=$(wc -l < file.txt)          # ✓ 一次调用
while read; do ((count++)); done   # ✗ 逐行解释执行,大文件慢几个数量级
1
2
3
4
5
6
7
8
9
10
11
12
13

# 变量引用

echo "$variable"        # 总是加引号:防词分割与通配展开
echo "${array[@]}"      # 数组同样加引号
1
2

# 临时文件

temp_file=$(mktemp)
trap 'rm -f "$temp_file"' EXIT    # 注意单引号,退出时才展开
echo "data" > "$temp_file"
1
2
3

# 常用工具函数

# 安全备份
backup_file() {
    local file="$1"
    [ -f "$file" ] && cp "$file" "${file}.bak.$(date +%Y%m%d_%H%M%S)"
}

# 目录可写且(文件不存在或文件可写)
is_writable() {
    local file="$1"
    [ -w "$(dirname "$file")" ] && { [ ! -f "$file" ] || [ -w "$file" ]; }
}

# 去除前后空格(纯内置实现)
trim() {
    local var="$1"
    var="${var#"${var%%[![:space:]]*}"}"    # 去前导
    var="${var%"${var##*[![:space:]]}"}"    # 去尾随
    echo "$var"
}

to_lower() { echo "${1,,}"; }    # bash 4+
to_upper() { echo "${1^^}"; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# link

  • runoob.com (opens new window) linux shell 学习
    • 运算符 (opens new window) : -z 等
    • Shell 流程控制 (opens new window)
  • Bash Guide (opens new window) - 全面的Bash指南
  • ShellCheck (opens new window) - Shell脚本静态分析工具
  • Google Shell Style Guide (opens new window) - Shell编程规范
#Shell#tutorial#Linux#base#cs-fundamentals
上次更新: 2026/10/18, 22:14:48
linux 入门指南
Shell 常用命令速查手册

← linux 入门指南 Shell 常用命令速查手册→

最近更新
01
Codex 使用与会话管理指南
10-17
02
Tailscale 入门:让自己的设备跨网络互联
10-15
03
GitHub CLI(gh)工作流与 CI 排查
10-08
更多文章>
Theme by Vdoing | Copyright © 2019-2026 Jacky | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式