Shell 到底是什么:操作系统留给你的那扇门

Shell 是操作系统留给用户的那道门:你敲下的命令、写出的脚本,都要先经过它解释,再交给内核执行。这篇 Shell 脚本从入门到精通的学习笔记,沿着“能用、写对、扛住”三个阶段把学习路径理一遍,同时看看 2025 年 7 月发布的 Bash 5.3 给日常脚本带来了哪些新东西。

从 sh 到 bash 的谱系

很多人第一次被 Shell 坑,是因为不知道 sh 和 bash 并不是一回事。

Shell常见路径特点
sh/bin/sh最早的 Unix 命令解释器之一,语法奠定今天的基础
bash/bin/bashGNU 重写并增强的版本,多数 Linux 发行版的默认交互 Shell
dash/usr/bin/dash体积小、启动快,Ubuntu 上 sh 实际指向它
zsh/usr/bin/zsh功能极其丰富,macOS 从 Catalina 起默认使用
fish/usr/bin/fish对用户友好,但语法与 bash 不兼容
ksh/bin/kshKorn Shell,商业 Unix 上的老朋友

这里最需要记住的一点:写脚本时如果 shebang 写的是 /bin/sh,那么在 Debian 和 Ubuntu 上真正执行它的很可能是 dash,bash 的扩展语法统统不能用。

为什么现在还要学 Shell

  • 几乎所有 Linux 服务器都自带,不需要额外安装任何运行时。
  • 部署、备份、日志清理、CI/CD 流水线这些活儿,往往一行命令抵一段程序代码。
  • 它是理解 Linux 的一把钥匙:管道、重定向、进程、权限,全都在这条命令线上体现。
不会 Shell 也能用 Linux,但会了 Shell,你会开始想把它用在每一件重复劳动上。

Bash 5.3:2025 年 7 月的这次更新

先看一张现状表,把版本坐标定下来。

项目现状
GNU Bash5.3,2025 年 7 月 5 日发布
上一稳定版5.2,2022 年 9 月
配套命令行库Readline 8.3
静态检查工具ShellCheck 0.11.0,2025 年 8 月
格式化工具shfmt,属于 mvdan/sh 项目
测试框架bats-core 1.12.0,2025 年 5 月
相关标准POSIX.1-2024,即 IEEE 1003.1-2024

从 2022 年 9 月的 5.2 到 2025 年 7 月的 5.3,中间隔了将近三年。官方公告里写着这一版包含“重要的新功能”,而它给出的东西确实没有让人失望。

不 fork 的命令替换

传统写法 $(命令) 会启动一个子 shell 来执行命令,再把它捕获回来。5.3 增加了两种新形式,直接在当前 shell 的执行上下文里跑,省掉一层 fork。

# 传统写法:启动一个子 shell,输出被捕获
result=$(date +%F)
echo "$result"

# 5.3 新增:在当前 shell 上下文执行并捕获输出,不 fork
result=${ date +%F; }
echo "$result"

# 5.3 新增:结果不返回,而是留在 REPLY 变量里
${| echo "hello"; }
echo "$REPLY"

对那种在一个循环里反复做命令替换的脚本,这两行省下的是实打实的进程开销。

补全与交互增强

  • 新增 GLOBSORT 变量,可以指定路径名补全结果按名称、大小、块数、修改时间、访问时间、状态变更时间或数值排序,还支持升序与降序。
  • compgen 内建命令新增选项,能把生成的补全候选写进指定变量,而不是直接打到屏幕上。
  • read 内建命令新增 -E 选项,读取输入时启用 Readline 的补全,连 Tab 补全都能用上,写交互式工具很方便。
  • source 内建命令新增 -p PATH,指定去哪个路径找要加载的文件,不再只认 PATH。
  • trap 新增 -P 选项,打印每个信号当前绑定的动作,调试信号处理时省事不少。

一批实用的零碎改进

  • 新增两个动态变量:BASH_MONOSECONDS 提供单调时钟秒数,BASH_TRAPSIG 保存当前 trap 对应的信号编号。
  • 命令计时的精度从 3 位小数提高到最多 6 位。
  • 多协程选项 MULTIPLE_COPROCS 现在默认开启。
  • 新增可加载内建命令 kv 和 strptime。
  • 新增 bash_source_fullpath 选项,让 Bash 把完整路径写进 BASH_SOURCE。
  • umask 内建命令补全了 POSIX 一致性所需的特性。
  • 当 [[ ]] 里用的正则表达式编译失败时,现在会打印错误信息,而不是默默判假。

升级前要留意的地方

  • posix 模式下,test 的 < 和 > 现在按字符串比较,不再有别的含义。
  • 源码标准升级到 C23,K&R 风格的编译器已经编译不了 Bash。
  • 检查脚本文件是否为二进制时,如果首行以 #! 开头,会先看前两行再判断。

怎么升级

Bash 5.3 不会立刻进入各发行版仓库,想尝鲜得从源码编译。

# 下载源码包
wget http://ftp.gnu.org/gnu/bash/bash-5.3.tar.gz
tar -xzf bash-5.3.tar.gz
cd bash-5.3

# 配置并编译,需要 gcc、make 等构建工具
./configure
make

# 安装到系统目录,生产机请先在测试环境验证
sudo make install

# 确认版本
bash --version
建议不要直接替换线上机器的默认 Shell。新版本先进测试机,跑一遍现有脚本,确认无误再考虑铺开。

十分钟写下第一个脚本

先确认环境

# 看看当前登录用的是哪个 Shell
echo "$SHELL"

# 看 Bash 的版本号
bash --version

# 系统里装着哪些 Shell
cat /etc/shells

第一个脚本

#!/usr/bin/env bash
# 上面这行叫 shebang,告诉系统用哪个解释器来执行这个文件

echo "你好,Shell"
echo "今天是 $(date +%F)"

给它加上执行权限再运行:

chmod +x hello.sh
./hello.sh

一条可靠的脚本骨架

脚本能不能长期维护,往往在开头几行就决定了。下面这份骨架可以直接拿去改成自己的脚本。

#!/usr/bin/env bash
#
# 脚本名:backup.sh
# 功能:把指定目录打包备份到目标目录
# 用法:./backup.sh 源目录 目标目录

set -euo pipefail
# -e          命令返回非零就立即退出,不再往下跑
# -u          使用未定义变量时报错,顺手抓出拼写错误
# -o pipefail 管道中任一命令失败,整条管道都算失败

readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

log() {
  # 日志统一写到标准错误,避免污染正常输出
  echo "[$(date +'%F %T')] $*" >&2
}

if [[ $# -ne 2 ]]; then
  log "用法:$0 源目录 目标目录"
  exit 64
fi

src="$1"
dst="$2"

if [[ ! -d "$src" ]]; then
  log "源目录不存在:$src"
  exit 66
fi

mkdir -p "$dst"
tar -czf "$dst/backup-$(date +%Y%m%d-%H%M%S).tar.gz" -C "$(dirname "$src")" "$(basename "$src")"
log "备份完成,输出到 $dst"

出错时记得收尾

脚本中途退出,临时文件不该留在磁盘上。trap 就是干这个的。

#!/usr/bin/env bash
set -euo pipefail

# 临时文件用完就删,脚本中途退出也能清理
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT

# 收到中断信号时给出提示,并用 130 退出
trap 'echo "已取消" >&2; exit 130' INT

echo "临时文件是:$tmp"

核心语法速通

变量与引用

name="世界"
greeting="你好,${name}"

# 双引号里的变量会被展开,单引号里原样保留
echo "$greeting"
echo '${name} 在单引号里不会被展开'

引用是 Shell 里最容易出错的地方。变量不加引号会被拆词、被通配符展开,路径里一旦有空格就崩。养成习惯:只要有变量,就加双引号。例外是 [[ ]] 内部不需要,以及 $* 和 $@ 这类本身有特殊含义的写法。

特殊变量速查

echo "脚本名:$0"
echo "参数个数:$#"
echo "第一个参数:$1"
echo "上一条命令的退出码:$?"

# "$*" 把所有参数拼成一个字符串,"$@" 保持每个参数独立完整
for arg in "$@"; do
  echo "参数:$arg"
done

退出码是 Shell 的命脉:0 表示成功,非 0 表示失败,if、&&、|| 全都靠它判断。命令替换用 $(),把一条命令的输出塞进变量:

today=$(date +%Y-%m-%d)
filecount=$(ls -1 | wc -l)
echo "今天是 $today,目录里有 $filecount 个条目"

参数展开:内置的字符串工具箱

Shell 自带一套完整的字符串操作语法,不用调用任何外部命令:

写法作用
${var:-默认值}var 为空时使用默认值
${var:?提示}var 为空时报错并退出
${var#前缀}从头删除最短匹配
${var##前缀}从头删除最长匹配
${var%后缀}从尾删除最短匹配
${var%%后缀}从尾删除最长匹配
${var/旧/新}替换第一个匹配
${var//旧/新}替换全部匹配
${#var}取字符串长度

实战演示,处理文件名最常用:

filename="report-2026.tar.gz"
echo "${filename#*-}"       # 2026.tar.gz,删掉最短的 xxx- 前缀
echo "${filename%.tar.gz}"  # report-2026,删掉 .tar.gz 后缀
echo "${filename%.*}"       # report-2026.tar,只删最后一个扩展名
echo "${filename//./_}"     # report-2026_tar_gz,点全部换成下划线
echo "${#filename}"         # 18,字符串长度

算术运算用双圆括号,注意和命令替换 $() 是两回事:

count=10
count=$((count + 1))     # 11
echo $(((3 + 4) * 2))    # 14

条件判断

bash 的条件判断首选双方括号 [[ ]]:不做单词拆分、支持模式匹配,文件名带空格也不会出事:

if [[ -f "$file" ]]; then
  echo "是个普通文件"
elif [[ -d "$file" ]]; then
  echo "是个目录"
else
  echo "不存在"
fi

常用测试运算符记几个就够:-f 存在且是文件、-d 存在且是目录、-z 字符串为空、-n 字符串非空、-e 存在。短路与 &&、或 || 是单行版的 if:

[[ -d "$dir" ]] || mkdir -p -- "$dir"    # 目录不存在就创建
cd "$dir" && ls -la

判断条件有三套写法,各有归属。

写法归属特点
testPOSIX最原始,结果通过退出码返回
[ ]POSIXtest 的别名,字符串相等用单个等号
[[ ]]bash 扩展支持正则与通配符匹配,不对变量拆词

写 bash 脚本时优先用 [[ ]]:它更安全,也不容易因为变量为空而报语法错。

循环

数字循环、逐行读文件、多分支匹配,三种套路覆盖九成场景:

# 数字序列循环
for i in {1..5}; do
  echo "第 $i 轮"
done

# 用通配符展开遍历文件
for f in /var/log/*.log; do
  echo "日志文件:$f"
done

# 逐行读文件:IFS= 保住行首空格,-r 防反斜杠转义,缺一不可
while IFS= read -r line; do
  echo "行内容:$line"
done < /etc/hosts

case 天生适合写命令子命令:

case "$1" in
  start)   echo "启动服务" ;;
  stop)    echo "停止服务" ;;
  restart) echo "重启服务" ;;
  *)       echo "用法:$0 start 或 stop 或 restart"; exit 1 ;;
esac

函数与参数

# 函数定义
deploy() {
  local service="$1"            # local 限定作用域,避免污染全局
  local version="${2:-latest}"  # 第二个参数没传时默认取 latest

  echo "准备部署 $service,版本 $version"
  return 0
}

deploy nginx 1.27
deploy redis

Shell 变量默认全局,函数里忘了 local 就会污染外面的世界:

backup_one() {
  local src=$1
  local dest=$2
  cp -- "$src" "$dest" || return 1
  echo "已备份:$src"
}

backup_one "a.txt" "backup/a.txt" || echo "备份失败" >&2

把前面的知识拼起来,就是一个可以直接用的批量备份脚本:

#!/usr/bin/env bash
# 把源目录下的 txt 文件备份到目标目录,报告成功个数
set -euo pipefail

src=${1:?用法:$0 源目录 目标目录}
dest=${2:?缺少目标目录}
mkdir -p -- "$dest"

count=0
for f in "$src"/*.txt; do
  [[ -e "$f" ]] || continue    # 一个 txt 都没有时跳过字面量
  cp -- "$f" "$dest/"
  count=$((count + 1))
done

echo "共备份 $count 个文件"

数组

servers=("web01" "web02" "web03")

echo "${servers[0]}"     # 第一个元素
echo "${#servers[@]}"    # 元素个数
echo "${servers[@]}"     # 全部元素

# 关联数组,键可以是字符串
declare -A ports=([http]=80 [https]=443)
echo "${ports[https]}"

管道、重定向与健壮性开关

三条数据通道

每个命令生来有三个通道:标准输入(0)、标准输出(1)、标准错误(2)。管道把前一个命令的输出接到后一个命令的输入,重定向把它们接到文件:

./job.sh > run.log 2>&1     # 输出和报错都写进 run.log,顺序不能颠倒
./job.sh 2> errors.log      # 只收集报错
./job.sh > /dev/null 2>&1   # 全部丢弃
./job.sh | tee run.log      # 屏幕和文件同时写

2>&1 必须写在 > run.log 之后——它的含义是“把通道 2 指向通道 1 现在指向的地方”,顺序反了报错就漏出去了。

set -euo pipefail 与 trap

开头那行健壮性开关拆开看:

set -euo pipefail
# -e:任何命令失败立即退出,不再硬着头皮往下跑
# -u:引用未定义的变量立即报错,抓住拼写错误
# -o pipefail:管道里任何一环失败,整条管道就算失败

默认情况下管道只看最后一环——grep foo big.log | head 里 grep 崩了,只要 head 正常,退出码照样是 0,事故就这么被吞掉。pipefail 专治这个。

临时文件用 trap 收尾,无论脚本怎么死都会执行清理:

tmpdir=$(mktemp -d)
cleanup() {
  rm -rf -- "$tmpdir"
}
trap cleanup EXIT    # 正常结束、报错退出、Ctrl+C,退出前都会触发

data="$tmpdir/data.csv"
echo "临时文件写到 $data"

这两招加上引号纪律,就是 Shell 脚本从“能跑”到“可靠”的全部秘密。

POSIX.1-2024 改了 sh 的哪部分

2024 年 6 月发布的 POSIX.1-2024(第 8 版)是 POSIX 标准四年一次的大更新,Shell 语言部分有三处值得注意:

  • $'...' 美元单引号写法正式标准化(bash 与 zsh 早就支持,现在是标准正文),单引号里的转义序列如换行符有了可移植写法。
  • timeout、realpath、readlink、gettext 系列进入必配工具清单——从这一版起,在跨平台脚本里放心用 timeout 300 ./job.sh 防挂死是符合标准的。
  • 记号识别规则收紧,[[ 与 ]] 被列入保留字但行为“结果未规定”——翻译成人话:跨平台 sh 脚本里依然别用双方括号,它不是标准语法。

写可移植脚本的姿势没变:目标环境按最老的算,bash 专属语法要么别用,要么老老实实声明 #!/usr/bin/env bash。

从入门到精通的学习路线

“精通”这个词听着唬人,拆开看其实是一层一层垒起来的台阶。

阶段目标建议投入
入门变量、引号、流程控制,能写几十行的日常小脚本一到两周
进阶吃透参数展开、管道重定向、set -euo pipefail 与 trap三到四周
工程化ShellCheck 全绿、shfmt 统一格式、bats 写测试、公共函数抽库一到两个月
精通精读 man bash 的展开章节,掌握进程替换与协程,徒手写可靠安装脚本长期

第一阶段:把命令用顺

目标是看懂别人的脚本,自己也能写出能跑的脚本。重点是这几块:

  • 引号:什么时候必须加双引号,什么时候用单引号。
  • 重定向:>、>>、2>、&>、<,还有 here document。
  • 管道:| 与 |&,以及用 PIPESTATUS 检查管道中每一段的退出码。
  • 命令替换、变量展开、通配符展开的时机与顺序。
  • 常用工具:grep、sed、awk、find、xargs、cut、sort、uniq。

第二阶段:写得对

能跑只是及格线,接下来要让它可靠、可读、可维护。

  • 严格模式:set -euo pipefail。
  • 用 shellcheck 做静态检查,把常见写法错误消灭在上线前。
  • 用 shfmt 统一缩进与换行,团队协作时省掉大量无意义的格式争论。
  • 参考 Google 的风格指南,把命名、注释、错误输出的习惯固定下来。

第三阶段:扛得住

这一步是从“会写脚本”走向“能维护系统”的分界线。

  • 用 bats-core 给关键脚本写测试,尤其是涉及删除、部署、权限修改的脚本。
  • 在 CI 里串起 shellcheck、shfmt、bats 三道检查。
  • 用 trap 处理清理和信号,保证脚本被打断时状态可控。
  • 设计好退出码和日志格式,让调用方和值班的人都能看懂。

第四阶段:读懂别人的脚本

GitHub 上有大量高质量的 Shell 脚本可供参考,比如 awesome-shell 和 awesome-bash 这两个精选列表整理了框架、工具、教程和实战项目。读别人的脚本时,重点看两件事:他们怎么组织代码结构,以及他们怎么处理错误和边界情况。

用 ShellCheck 和 shfmt 守住代码质量

ShellCheck 静态检查

# 安装,不同平台命令不同
sudo apt install shellcheck     # Debian 或 Ubuntu
brew install shellcheck         # macOS

# 直接检查一个脚本
shellcheck backup.sh

# 脚本以 sh 开头时,指定方言
shellcheck -s sh legacy.sh

# 只报告严重级别较高的告警
shellcheck -S warning backup.sh

# 允许跟随 source 进来的文件一起分析
shellcheck -x lib/common.sh

ShellCheck 0.11.0 于 2025 年 8 月发布,新增了 SC2327 到 SC2332 一批规则。比如 SC2327 会警告 var=$(cmd > file) 这种“捕获了重定向输出、结果永远是空”的写法,SC2329 会提示从未被调用的函数。这一版还跟进了 Bash 5.3,认得新的命令替换形式和 source -p。另外,被吐槽多年的 SC2002(无用 cat)从这一版起默认不再提示,需要 --enable=useless-use-of-cat 才能打开。

遇到确实需要保留的写法,可以在脚本里定向关闭某条规则。

# 在单独一行关掉某条规则,作用于下一行
# shellcheck disable=SC2086
echo $unquoted_var

# 也可以在文件开头声明,整份文件生效
# shellcheck disable=SC1091

shfmt 统一格式

# 用 Go 安装最新版本
go install mvdan.cc/sh/v3/cmd/shfmt@latest

# 格式化并直接写回文件
shfmt -w backup.sh

# 用两空格缩进
shfmt -i 2 -w backup.sh

# 只列出格式不合规的文件,适合放进流水线做检查
shfmt -l .

shfmt 是 mvdan/sh 项目的一部分,能按统一风格重排缩进、空格和换行,还支持 --simplify 简化冗余写法、--minify 压缩体积。它和主流编辑器集成之后,可以做到保存即格式化。

编辑器与流水线

VS Code 里装上 shell-format 和 shellcheck 插件,保存时自动格式化加检查,问题基本当场就能看见。CI 里则通常是这样一条检查链。

# 静态检查,-x 允许跟随 source 的文件
shellcheck -x scripts/*.sh

# 只报告格式差异,不修改文件
shfmt -d -l scripts/

# 跑测试用例
bats tests/*.bats

给脚本写测试:bats-core

bats-core 是 Bash Automated Testing System,用 Bash 就能写测试,输出兼容 TAP,可以直接接进各类 CI 平台。2025 年 5 月发布的 v1.12.0 增加了 bats::on_failure 钩子,测试或 setup 失败时可以统一收集现场信息,排查跨环境问题比以前方便。

#!/usr/bin/env bats
# 文件名:backup.bats

setup() {
  # 每个用例执行前都会运行,这里准备一个临时目录
  TEST_DIR="$(mktemp -d)"
}

teardown() {
  # 用例结束后清理现场
  rm -rf "$TEST_DIR"
}

@test "缺少参数时返回用法错误" {
  run ./backup.sh
  [ "$status" -eq 64 ]
  [[ "$output" == *"用法"* ]]
}

@test "源目录不存在时给出提示" {
  run ./backup.sh /no/such/dir "$TEST_DIR"
  [ "$status" -eq 66 ]
}

测试里用 run 执行被测命令,结果会分别落在 $status 和 $output 里,判断起来和写普通 Shell 判断没什么区别。对一段会删除文件的清理脚本来说,这点测试投入非常划算。

新手最容易踩的坑

  • 变量不加引号。 for f in $(ls) 遇到带空格的文件名直接乱套,rm $file 更可能删错东西。遍历文件用 glob:for f in ./*,引用一律 "$f"。
  • 把 bash 语法写进 #!/bin/sh 脚本。 [[ ]]、==、source、数组、{1..5}、set -o pipefail 全是 bash 专属。dash 一接手,报错报得莫名其妙。跨平台脚本按 POSIX 最小集写。
  • read 忘了 -r 和 IFS=。 少了它们,行首空格丢失、反斜杠变成转义符。逐行读文件的固定咒语是 while IFS= read -r line。
  • set -e 不是万能保险。 if 条件里的命令、&& 与 || 链中的命令失败不会触发退出,管道默认只看最后一环。关键步骤自己检查退出码。
  • rm -rf "$dir"/* 的空变量灾难。 dir 为空时这条命令变成删除根目录下所有内容。防御写法:rm -rf -- "${dir:?dir 未设置}"/*,变量为空当场报错。
  • Windows 编辑器带来的 CRLF。 在 Windows 记事本里写的脚本,每行行尾带 \r,Linux 上报 command not found 且找不到原因。编辑器切 LF 行尾,或 sed -i 's/\r$//' script.sh 一键修复。
  • cd 不检查返回值。 目录不存在时 cd 失败,脚本继续在原目录里执行后续命令,文件写错地方。固定写法 cd "$dir" || exit 1。
  • 函数里忘了 local。 Shell 变量默认全局,函数里的临时变量会把外面的同名变量悄悄改掉。
  • 长任务不挂 timeout。 网络请求、子进程可能永远不返回。POSIX.1-2024 起 timeout 已是标配工具:timeout 300 ./job.sh。
  • 在脚本里依赖交互式配置。 alias 与部分环境变量只在交互 Shell 生效,cron 与 CI 环境里一个都不存在。脚本开头显式设置所有依赖的变量。

工具链与学习资料

官方文档与手册

风格指南与最佳实践

  • Google 发布的 Shell 风格指南,团队协作时很值得参考:Google Shell Style Guide
  • 系统讲解 Bash 概念与陷阱的社区指南:BashGuide
  • 常见问题合集,几乎每个坑都能在这里找到答案:BashFAQ
  • 用纯 Bash 内建实现常见任务的技巧集:pure-bash-bible

常用工具

书与长文

  • 《Linux 命令行大全》适合零基础起步,把命令行的用法讲得很系统。
  • 《bash Cookbook》偏向实战菜谱,遇到具体问题可以直接查。
  • 免费的英文电子书 The Linux Command Line,从命令行讲到脚本,是零基础最顺的路径:linuxcommand.org
  • 老牌的 Advanced Bash-Scripting Guide 内容仍然完整,但更新已经停滞多年,里面的例子要注意对照新版手册。
一个实用的建议:别把资料收藏当学习。挑一个自己每天都在重复的操作,用脚本把它自动化掉,遇到卡点再去查手册,这样学得最快。

和 Python 比,什么时候该用 Shell

这两个常被放在一起比较,答案其实取决于任务性质。

任务类型更合适的选择
调用现成命令做流程编排Shell
部署、备份、日志清理Shell
管道式文本过滤与统计Shell
复杂数据结构与算法Python
需要单元测试与类型检查Python
网络请求与 API 集成Python
超过几百行且逻辑复杂Python

Google 的风格指南里有一条很直白的建议:脚本一旦超过一百行,或者用上了不直观的控制流,就该考虑换成更结构化的语言。Shell 的强项是调用命令,弱项是数据结构和错误处理,认清这条边界,选择就不难了。

Shell 是胶水,不是砖头。用它把现成的工具粘起来,而不是拿它盖楼。

写在最后

Shell 是一门“看起来简单、写对很难”的语言。它的坑大多不在语法本身,而在引号、退出码、子进程这些看不见的细节里——一段二十行的清理脚本,破坏范围可能远大于几百行的报表脚本。

Shell 脚本的“精通”不是背下多少语法,而是三件事:引号纪律刻进肌肉记忆、退出码链条想得比命令本身多一层、每个脚本默认带上 set -euo pipefail 与 trap。2025 年的 Bash 5.3 也没有加什么花哨概念,反而在命令替换去 fork、补全排序、计时精度这些地方做了改进。对一门诞生快五十年的语言来说,这种克制的更新节奏挺难得。

如果你刚开始学,建议先把两件事变成肌肉记忆:脚本开头写 set -euo pipefail,写完立刻过一遍 shellcheck。至于“精通”,那通常是从某件非做不可的重复劳动开始的。

发表评论

验证码图片,点击可更换