Skip to content

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
# 处理: c

shift 在处理可变数量参数时有用——循环里每次处理 $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.log

getopts "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 $@ $# 配合 shiftgetopts,能处理从简单到复杂的参数。参数校验让脚本在错误输入时早失败,不跑到一半才出错。

下一篇讲条件判断——根据参数和命令结果做分支。