2025-07-27 | 研究与探索 | UNLOCK

如何完美操作 POSIX shell 的命令行参数

众所周知数组并非 POSIX sh 的一部分,而是 ksh,bash 等的扩展语法。像 ash,dash 等实现就不支持数组。

现在有这么一个需求:需要写一个脚本 myls 代替,编辑命令行参数,将所有的 -q 删掉,把剩下的参数原样转发给 busybox ls。比如:

1
myls -q a b

要将之变换成

1
busybox ls a b

还好 POSIX sh 有一个“当前参数数组”的概念,可以用 $1$2 这样的方式访问,"$@" 可以完美转发所有参数,不会有空格问题,还有 set 命令可 以用来修改当前参数,但只能一次设置全体参数,无法像数组那样单独修改某个位置。传入的 -q 可能在任意位置,这时我们需要用到 sh 的另一个特性, for 循环会把要循环的字符串存储一个副本,因此在用 for 循环遍历当前参数时,同时用 set 修改当前参数是安全的。另外,for x do 等同于 for x in "$@"; do,可以直接遍历当前参数。

这样,我们可以一边遍历当前参数,一边将不等于 -q 的参数添加到当前参数数组的末尾,再用 shift 命令删除初始参数,最后将当前参数传给目标。

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/sh
argc=$#
for arg do
case "$arg" in "-q")
continue
esac
set -- "$@" "$arg"
done
# 删除初始参数
shift "$argc"
# 将当前参数传给 busybox ls
busybox ls "$@"

另外我们还可以在第一次循环时清空当前参数,这样就不需要在最后用 shift 删除初始参数了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/sh
first=
for arg do
case "$first" in '')
first=1
# 清空当前参数
set --
esac
case "$arg" in "-q")
continue
esac
set -- "$@" "$arg"
done
busybox ls "$@"

此处需要 first 变量,我没找到更简单的方法来判断是否是第一次循环。