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
        • 常见 Shell 种类
        • POSIX 是什么
        • GNU 是什么
        • Shebang(#!/)
        • 脚本头部模板
      • 变量与参数
        • 特殊变量
        • 变量默认值
        • 字符串操作
        • 数组
        • 关联数组(bash 4+)
      • 执行模型
      • 函数与流程
        • 函数
        • test 命令
        • 参数验证
      • 错误处理与健壮性
      • 实用片段
        • PATH 修改
        • 常用小命令
        • 交互配置
        • 输出着色
        • 调试
      • 性能与最佳实践
        • 性能要点
        • 变量引用
        • 临时文件
      • 常用工具函数
      • link
      • 引用与展开
      • 流程控制
      • 权限与文件语义
    • Shell 执行模型与环境加载
    • Shell 错误处理、调试与资源清理
    • Shell 常用命令速查手册
    • Shell 代码片段集合
  • 安全

  • 设计模式

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

shell 入门指南

本目录代码默认采用 Bash 3.2+;Bash 4+ 特性另行标明。macOS 交互终端通常为 Zsh,脚本解释器以实际调用方式为准;外部命令还需区分 GNU/BSD 实现。

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

# Shell 简介

# 什么是 Shell

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

# 常见 Shell 种类

Shell 全称 特点 适用场景
sh Bourne Shell 历史上的 Bourne Shell;现代 /bin/sh 通常由兼容 POSIX 的实现提供 跨平台脚本,最小兼容
bash Bourne Again Shell GNU 项目的 shell,sh 的增强版 许多 Linux 发行版采用,功能丰富
zsh Z Shell 独立发展的 Shell,支持强补全与交互定制 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 兼容性踩坑:同一脚本本地能跑、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 有上下文例外,见错误处理指南
# 按语义局部设置 IFS;正确引用变量,不能靠全局 IFS 替代
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:-$HOME/.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 索引数组语法,下标从 0 开始。Zsh 也支持数组,但默认从 1 开始;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、进程替换、Here Document、重定向和逐行读取见 Shell 执行模型与环境加载。

# 函数与流程

# 函数

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
        if [ -f "$file" ]; then count=$((count + 1)); fi
    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

# 错误处理与健壮性

严格模式的适用边界、错误降级、信号清理和作业控制见 Shell 错误处理指南。

# 实用片段

# 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 file            # 读取链接目标文本;递归规范化用 realpath(先确认系统支持)
type cmd                  # 显示命令类型(别名/函数/内建/文件)
command -v cmd            # 查找命令路径(POSIX 标准,脚本判断命令存在的首选)
1
2
3
4
5
6
7
8

# 交互配置

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

# 输出着色

color_echo() {
    printf '\033[%sm%s\033[0m\n' "$1" "$2"
}
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 IFS= read -r line; do count=$((count + 1)); done < file.txt   # ✗ 逐行解释执行,大文件慢几个数量级
1
2
3
4
5
6
7
8
9
10
11
12
13

# 变量引用

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

# 临时文件

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

# 常用工具函数

# 安全备份
backup_file() {
    local file="$1"
    local target
    [ -f "$file" ] || return 1
    target=$(mktemp "${file}.bak.XXXXXX") || return 1
    if cp "$file" "$target"; then printf '%s\n' "$target"; else rm -f "$target"; return 1; fi
}

# 目录可写且(文件不存在或文件可写)
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
23
24
25

# 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编程规范

# 引用与展开

写法 展开与边界 示例
'…' 全部原样保留 printf '%s\n' '$HOME' 输出 $HOME
"…" 参数/命令/算术展开;保留展开结果为一个参数 printf '%s\n' "$HOME"
$'…' Bash/Zsh 的转义字符串 $'a\nb' 含换行;非 POSIX 基础语法
$(cmd) 捕获 stdout,删除末尾换行 name=$(basename "$path");另查退出码
$((expr)) 算术展开 count=$((count + 1))
*.txt 未引用的通配符匹配路径 不匹配时 Bash 默认保留字面量,Zsh 默认报错

Bash 中未引用的参数/命令展开通常还会经过 IFS 分词、路径名展开。x='a b'; printf '<%s>\n' "$x" 得到一个参数,省略引号会拆成两个。Zsh 默认普通标量不做同样的分词,不能据此推断 Bash。"${ZSH_CUSTOM:-~/.oh-my-zsh/custom}" 中的 ~ 不会展开,应使用 $HOME。

glob(如 *.txt)、正则(如 ^v[0-9]+$)、字面量是三种接口;${var//pattern/replacement} 中 pattern 也是 Shell 模式,不能把用户传入的任意文本直接当模式。

# 流程控制

结构 用途与最小示例
if / elif / else if [ -f "$file" ]; then printf '%s\n' found; else printf '%s\n' missing; fi
case case "$mode" in start) start_service ;; stop) stop_service ;; *) exit 2 ;; esac
for for file in "$@"; do printf '%s\n' "$file"; done;逐参数遍历
while while IFS= read -r line || [ -n "$line" ]; do printf '%s\n' "$line"; done < "$file";保留末行无换行的内容
until until check_ready; do sleep 1; done;正式脚本增加重试次数与超时
break / continue 退出循环/跳过本轮;可带循环层数
&& / || 按退出状态执行后续命令;a && b \|\| c 中 b 失败也会执行 c,并非完整 if/else

函数默认返回最后执行命令的状态。日志不要掩盖关键操作失败;local result=$(cmd) 会让 local 的状态遮蔽 cmd,应先 local result 再赋值并检查。

# 权限与文件语义

命令/概念 用途与边界
chmod u+x script.sh 允许直接执行;bash script.sh 只要求能读脚本,解释器由命令指定
chmod 600 config / chmod 700 directory 分别限制文件和目录仅所有者访问
umask 077 当前进程及后代创建对象时屏蔽组/其他人权限;不改变已有权限
目录 r / w / x 列出名称/增删目录项/穿越访问;删除主要取决于父目录权限及 sticky bit 等约束
ln / ln -s / readlink 硬链接、符号链接及查看目标文本;见 ln 与 readlink

备份函数的完整契约与重复备份处理见 代码片段:文件备份;日志、着色示例分别保留简版和按级别/颜色名的版本,按接口选择。

依据:Bash 引用规则 (opens new window)、展开顺序 (opens new window)、参数展开 (opens new window)。

迁移前的章节入口

保留旧文章的章节锚点,旧书签可从这里继续前往专题正文。

  • 父子 Shell

  • 进程替换

  • Here Document

  • 重定向

  • IFS 与逐行读取

  • 严格模式

  • 错误捕获与降级

  • 信号与清理

  • 作业控制
#Shell#tutorial#Linux#base#cs-fundamentals
上次更新: 2026/10/26, 22:32:55
Shell 学习与使用导航
Shell 执行模型与环境加载

← Shell 学习与使用导航 Shell 执行模型与环境加载→

最近更新
01
Wrangler 使用指引
11-06
02
ADB 图形化与手机端调试工具选型
10-29
03
Shell 执行模型与环境加载
10-26
更多文章>
Theme by Vdoing | Copyright © 2019-2026 Jacky | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式