Shell Scripting
Write reliable, maintainable bash scripts. Covers argument parsing, error handling, portability, temp files, parallel execution, process management, and self-documenting scripts.
When to Use
- - Writing scripts that others (or future you) will run
- Automating multi-step workflows
- Parsing command-line arguments with flags and options
- Handling errors and cleanup properly
- Running tasks in parallel
- Making scripts portable across Linux and macOS
- Wrapping complex commands with a simpler interface
Script Template
CODEBLOCK0
Error Handling
set flags
CODEBLOCK1
Trap for cleanup
CODEBLOCK2
Error handling patterns
CODEBLOCK3
Argument Parsing
Simple: positional + flags
CODEBLOCK4
getopts (POSIX, short options only)
CODEBLOCK5
Temp Files and Directories
CODEBLOCK6
Parallel Execution
xargs -P
CODEBLOCK7
Background jobs + wait
CODEBLOCK8
GNU Parallel (if available)
CODEBLOCK9
Process Management
Background processes
CODEBLOCK10
Process supervision
CODEBLOCK11
Timeout
CODEBLOCK12
Portability (Linux vs macOS)
Common differences
CODEBLOCK13
POSIX-safe patterns
CODEBLOCK14
Config File Parsing
Source a config file
CODEBLOCK15
Parse INI-style config
CODEBLOCK16
Useful Patterns
Confirm before destructive action
CODEBLOCK17
Progress indicator
CODEBLOCK18
Lock file (prevent concurrent runs)
CODEBLOCK19
Stdin or file argument
CODEBLOCK20
Tips
- - Always start with
set -euo pipefail. It catches 80% of silent bugs. - Always use
trap cleanup EXIT for temp files. Never rely on reaching the cleanup code at the end. - Quote all variable expansions:
"$var" not $var. Unquoted variables break on spaces and globs. - Use
[[ ]] instead of [ ] in bash. It handles empty strings, spaces, and pattern matching better. - INLINECODE6 is the best linter for shell scripts. Run it:
shellcheck myscript.sh. Install it if available. - INLINECODE8 for constants prevents accidental overwrite:
readonly DB_HOST="localhost". - Write a
usage() function and call it on -h/--help and on missing required arguments. Future users (including you) will thank you. - Prefer
printf over echo for anything that might contain special characters or needs formatting. - Test scripts with
bash -n script.sh (syntax check) before running.
Shell Scripting
编写可靠、可维护的bash脚本。涵盖参数解析、错误处理、可移植性、临时文件、并行执行、进程管理和自文档化脚本。
何时使用
- - 编写供他人(或未来的你)运行的脚本
- 自动化多步骤工作流程
- 解析带标志和选项的命令行参数
- 正确处理错误和清理工作
- 并行运行任务
- 使脚本在Linux和macOS之间可移植
- 用更简单的接口封装复杂命令
脚本模板
bash
#!/usr/bin/env bash
set -euo pipefail
描述:此脚本的功能(一行)
用法:script.sh [选项] <必需参数>
readonly SCRIPTDIR=$(cd $(dirname ${BASHSOURCE[0]}) && pwd)
readonly SCRIPT_NAME=$(basename $0)
默认值
VERBOSE=false
OUTPUT_DIR=./output
usage() {
cat <
用法:$SCRIPT_NAME [选项] <输入文件>
描述:
处理输入文件并生成输出。
选项:
-o, --output 目录 输出目录(默认:$OUTPUT_DIR)
-v, --verbose 启用详细输出
-h, --help 显示此帮助信息
示例:
$SCRIPT_NAME data.csv
$SCRIPT_NAME -v -o /tmp/results data.csv
EOF
}
log() { echo [$(date +%H:%M:%S)] $* >&2; }
debug() { $VERBOSE && log DEBUG: $* || true; }
die() { log ERROR: $*; exit 1; }
解析参数
while [[ $# -gt 0 ]]; do
case $1 in
-o|--output) OUTPUT_DIR=$2; shift 2 ;;
-v|--verbose) VERBOSE=true; shift ;;
-h|--help) usage; exit 0 ;;
--) shift; break ;;
-*) die 未知选项:$1 ;;
*) break ;;
esac
done
INPUT_FILE=${1:?$(usage >&2; echo 错误:需要输入文件)}
[[ -f $INPUTFILE ]] || die 文件未找到:$INPUTFILE
主逻辑
main() {
debug 输入:$INPUT_FILE
debug 输出:$OUTPUT_DIR
mkdir -p $OUTPUT_DIR
log 正在处理 $INPUT_FILE...
# ... 执行工作 ...
log 完成。输出在 $OUTPUT_DIR
}
main $@
错误处理
set 标志
bash
set -e # 任何命令失败时退出
set -u # 使用未定义变量时报错
set -o pipefail # 管道中任何命令失败则管道失败
set -x # 调试:执行前打印每条命令(较嘈杂)
组合使用(在每个脚本中使用)
set -euo pipefail
临时禁用允许失败的命令
set +e
some
commandthat
mightfail
exit_code=$?
set -e
陷阱用于清理
bash
退出时清理(任何退出:成功、失败或信号)
TMPDIR=
cleanup() {
[[ -n $TMPDIR ]] && rm -rf $TMPDIR
}
trap cleanup EXIT
TMPDIR=$(mktemp -d)
自由使用 $TMPDIR — 它会自动清理
捕获特定信号
trap echo 已中断; exit 130 INT # Ctrl+C
trap echo 已终止; exit 143 TERM # kill
错误处理模式
bash
使用前检查命令是否存在
command -v jq >/dev/null 2>&1 || die 需要 jq 但未安装
提供默认值
NAME=${NAME:-default_value}
必需变量(未设置则失败)
: ${API
KEY:?错误:需要 APIKEY 环境变量}
重试命令
retry() {
local max_attempts=$1
shift
local attempt=1
while [[ $attempt -le $max_attempts ]]; do
$@ && return 0
log 尝试 $attempt/$max_attempts 失败。正在重试...
((attempt++))
sleep $((attempt * 2))
done
die 命令在 $max_attempts 次尝试后失败:$*
}
retry 3 curl -sf https://api.example.com/health
参数解析
简单:位置参数 + 标志
bash
手动解析(无依赖)
FORCE=false
DRY_RUN=false
while [[ $# -gt 0 ]]; do
case $1 in
-f|--force) FORCE=true; shift ;;
-n|--dry-run) DRY_RUN=true; shift ;;
-o|--output)
[[ -n ${2:-} ]] || die --output 需要一个值
OUTPUT=$2; shift 2 ;;
--output=*)
OUTPUT=${1#*=}; shift ;;
-h|--help) usage; exit 0 ;;
--) shift; break ;; # 选项结束
-*) die 未知选项:$1 ;;
*) break ;; # 位置参数开始
esac
done
剩余参数是位置参数
FILES=($@)
[[ ${#FILES[@]} -gt 0 ]] || die 至少需要一个文件
getopts(POSIX,仅短选项)
bash
while getopts :o:vhf opt; do
case $opt in
o) OUTPUT=$OPTARG ;;
v) VERBOSE=true ;;
f) FORCE=true ;;
h) usage; exit 0 ;;
:) die 选项 -$OPTARG 需要一个参数 ;;
?) die 未知选项:-$OPTARG ;;
esac
done
shift $((OPTIND - 1))
临时文件和目录
bash
创建临时文件(自动唯一)
TMPFILE=$(mktemp)
echo data > $TMPFILE
创建临时目录
TMPDIR=$(mktemp -d)
创建带自定义前缀/后缀的临时文件
TMPFILE=$(mktemp /tmp/myapp.XXXXXX)
TMPFILE=$(mktemp --suffix=.json) # 仅 GNU
始终使用陷阱清理
trap rm -f $TMPFILE EXIT
可移植模式(在 macOS 和 Linux 上均有效)
TMPDIR=$(mktemp -d 2>/dev/null || mktemp -d -t myapp)
trap rm -rf $TMPDIR EXIT
并行执行
xargs -P
bash
并行运行4个命令
cat urls.txt | xargs -P 4 -I {} curl -sO {}
并行处理文件(一次4个)
find . -name *.csv | xargs -P 4 -I {} ./process.sh {}
带进度指示器的并行处理
find . -name *.jpg | xargs -P 8 -I {} sh -c convert {} -resize 800x600 resized/{} && echo 完成:{}
后台作业 + wait
bash
在后台运行任务,等待所有完成
pids=()
for file in data/*.csv; do
process_file $file &
pids+=($!)
done
等待所有并检查结果
failed=0
for pid in ${pids[@]}; do
wait $pid || ((failed++))
done
[[ $failed -eq 0 ]] || die $failed 个作业失败
GNU Parallel(如果可用)
bash
使用8个并行作业处理文件
parallel -j 8 ./process.sh {} ::: data/*.csv
带进度条
parallel --bar -j 4 convert {} -resize 800x600 resized/{/} ::: *.jpg
管道输入行
cat urls.txt | parallel -j 10 curl -sO {}
进程管理
后台进程
bash
在后台启动
long
runningcommand &
BG_PID=$!
检查是否仍在运行
kill -0 $BG_PID 2>/dev/null && echo 运行中 || echo 已停止
等待它
wait $BG_PID
echo 退出码:$?
脚本退出时终止
trap kill $BG_PID 2>/dev/null EXIT
进程监控
bash
运行命令,如果死亡则重启
run
withrestart() {
local cmd=($@)
while true; do
${cmd[@]} &