Appearance
04|位置参数与内置变量
前面几篇的脚本都是写死的内容——清理固定的 /var/log、备份固定的目录。但实际用脚本时往往要传参:backup.sh /data 备份这个目录、deploy.sh web-01 部署到这台机器、clean.sh 7 删除 7 天前的日志。
脚本接收外部传进来的参数,靠位置参数。本篇讲怎么读参数、怎么处理可变数量的参数。
一、位置参数
脚本运行时传的参数,按顺序用 $1、$2、$3 访问:
bash
#!/usr/bin/env bash
echo "脚本名: $0"
echo "第一个参数: $1"
echo "第二个参数: $2"跑一下:
bash
./show.sh web-01 8080
# 脚本名: ./show.sh
# 第一个参数: web-01
# 第二个参数: 8080$0 是脚本名,$1 起是参数。
参数个数超过 9 个要用花括号:$10 会被解析成 $1 后接 0,得写成 ${10}。
参数没传会得到空值。配合上一篇的 set -u,引用没传的参数会报错:
bash
set -u
echo "$1" # 没传参数时报错退出:1: unbound variable要给参数设默认值,用 ${1:-默认值}:
bash
days=${1:-7} # 第一个参数没传就用 7
echo "删除 ${days} 天前的日志"二、参数个数和所有参数
| 变量 | 含义 |
|---|---|
$# | 参数个数 |
$@ | 所有参数,每个参数是独立词 |
$* | 所有参数,拼成一个词 |
$@ 和 $* 的区别在加引号时才显现:
bash
#!/usr/bin/env bash
set -euo pipefail
echo "参数个数: $#"
echo "用 \$@ 遍历:"
for arg in "$@"; do
echo " $arg"
done
echo "用 \$* 一次输出:"
echo " $*"跑一下,故意传一个含空格的参数:
bash
./args.sh hello "world shell" 123
# 参数个数: 3
# 用 $@ 遍历:
# hello
# world shell
# 123
# 用 $* 一次输出:
# hello world shell 123"$@" 保持每个参数的完整性("world shell" 仍是一个词),"$*" 把所有参数用空格拼成一个大字符串。遍历参数几乎总是用 "$@"。
三、shift:消费参数
shift 把参数往左移一位——$2 变 $1,$3 变 $2,原来的 $1 丢弃:
bash
#!/usr/bin/env bash
while [ $# -gt 0 ]; do
echo "处理: $1"
shift
done跑一下:
bash
./shift.sh a b c
# 处理: a
# 处理: b
# 处理: cshift 在处理可变数量参数时有用——循环里每次处理 $1,然后 shift,直到参数处理完。
四、处理带选项的参数
实际脚本经常要处理 -d 7(带值的选项)和 -v(开关选项)这种参数。手写解析容易乱,用 getopts 内置命令:
bash
#!/usr/bin/env bash
set -euo pipefail
days=7
verbose=false
while getopts "d:v" opt; do
case $opt in
d) days=$OPTARG ;;
v) verbose=true ;;
?) echo "用法: $0 [-d days] [-v]" >&2; exit 1 ;;
esac
done
shift $((OPTIND - 1)) # 移走已解析的选项,剩下的位置参数在 $@
echo "days=$days verbose=$verbose"
echo "剩余参数: $@"跑一下:
bash
./opts.sh -d 30 -v file1.log file2.log
# days=30 verbose=true
# 剩余参数: file1.log file2.loggetopts "d:v" 里 d: 表示 d 选项带值,v 不带冒号表示是开关。$OPTARG 存当前选项的值,$OPTIND 记录解析到第几个参数,shift $((OPTIND-1)) 把已解析的选项移走,剩下的位置参数留给脚本主体用。
五、$$ 和 $!:进程号
| 变量 | 含义 |
|---|---|
$$ | 当前脚本的进程号 |
$! | 最近一个后台进程的进程号 |
$$ 常用来生成唯一的临时文件名,避免并发冲突:
bash
tmpfile="/tmp/work.$$"
echo "临时文件: $tmpfile"
# 临时文件: /tmp/work.12345$! 在启动后台任务后用,记录 PID 方便后续等待或终止:
bash
tar -czf backup.tar.gz /data &
tar_pid=$!
# ... 干别的事
wait $tar_pid && echo "备份完成"六、参数校验
生产脚本接收参数后要先校验,避免在错误参数下跑出意外:
bash
#!/usr/bin/env bash
set -euo pipefail
target_dir=${1:?用法: $0 <目录>}
if [ ! -d "$target_dir" ]; then
echo "错误: $target_dir 不是目录" >&2
exit 1
fi
echo "处理目录: $target_dir"${1:?...} 既能设默认提示又能在参数为空时报错退出。没传参数时:
bash
./clean.sh
# ./clean.sh: 1: 用法: ./clean.sh <目录>学会了之后
位置参数让脚本能接收外部输入,不再写死。$1 $@ $# 配合 shift 和 getopts,能处理从简单到复杂的参数。参数校验让脚本在错误输入时早失败,不跑到一半才出错。
下一篇讲条件判断——根据参数和命令结果做分支。