在运维工作中,编写 Shell 脚本处理命令行参数几乎是家常便饭。但很多朋友一遇到复杂的参数组合——比如长短选项、辅助参数、布尔开关——就有些头疼。其实,掌握几种经典模式后就能轻松应对。如果你想系统提升脚本处理能力,云栈社区上有大量实战经验分享。
下文将围绕 Bash 参数处理,由浅入深地拆解 while + case + shift 组合拳、for 循环解析、包装脚本设计,以及如何把字符串快速切分成数组。
23.1 多参数解析
当你需要解析大量参数时,最推荐的方法是利用 while 循环搭配 case 语句和 shift。
shift 的作用是“弹出”参数列表的第一个元素,原本的 $2 会变成 $1,$3 变 $2,以此类推。这让我们可以逐个消耗参数,非常适合顺序解析。
#!/bin/bash
# 加载用户定义的参数
while [[ "$#" -gt 0 ]]; do
case $1 in
-a|--valueA)
valA="$2"
shift
;;
-b|--valueB)
valB="$2"
shift
;;
--help|*)
echo "Usage:"
echo " --valueA \"value\""
echo " --valueB \"value\""
echo " --help"
exit 1
;;
esac
shift
done
echo "A: $valA"
echo "B: $valB"
输入与输出演示:
$ ./multipleParams.sh --help
Usage:
--valueA "value"
--valueB "value"
--help
$ ./multipleParams.sh
A:
B:
$ ./multipleParams.sh --valueB 2
A:
B: 2
$ ./multipleParams.sh --valueB 2 --valueA "hello world"
A: hello world
B: 2
23.2 使用 for 循环解析参数
还有一种更直观的思路——用 for 循环逐项检查 $@。以下脚本提供了几个典型选项:
| 选项 |
替代形式 |
描述 |
-h |
--help |
显示帮助 |
-v |
--version |
显示版本信息 |
-dr |
--doc-root |
带有一个辅助参数(路径)的选项 |
-i |
--install |
布尔选项(true/false) |
* |
- |
无效选项 |
#!/bin/bash
dr=''
install=false
skip=false
for op in "$@"; do
if $skip; then
skip=false
continue
fi
case "$op" in
-v|--version)
echo "$ver_info"
shift
exit 0
;;
-h|--help)
echo "$help"
shift
exit 0
;;
-dr|--doc-root)
shift
if [[ "$1" != "" ]]; then
dr="${1%/}" # 移除末尾的斜杠
shift
skip=true
else
echo "E: Arg missing for -dr option"
exit 1
fi
;;
-i|--install)
install=true
shift
;;
*)
echo "E: Invalid option: $1"
shift
exit 1
;;
esac
done
这里的 skip 标记用于跳过已作为辅助参数消耗掉的元素,避免遍历混乱。你是否也曾因为遗漏类似逻辑而抓狂?
23.3 包装脚本
包装脚本(Wrapper Script)本质上是对另一个脚本或命令的封装,用来提供额外功能或简化常用操作。
例如,在一些较新的 GNU/Linux 系统中,真实的 egrep 早已被一个包装脚本取代。它的内容可以精简到这种程度:
#!/bin/sh
exec grep -E "$@"
所以,当你在这些系统上敲下 egrep 时,实际运行的是 grep -E,并且所有参数原封不动地转发过去。
推而广之,如果你想用一个叫 mexp 的脚本来包装 expm 命令,只需这样写:
#!/bin/sh
expm "$@" # 在 "$@" 之前可以添加其他选项
# 或者
# full/path/to/expm "$@"
是不是比想象中简单得多?
23.4 访问参数
Bash 中传递给脚本的参数按位置命名:$1 是第一个参数,$2 是第二个,缺省的参数会直接展开为空字符串。检查参数是否存在的常用写法如下:
if [ -z "$1" ]; then
echo "No argument supplied"
fi
获取所有参数
$@ 和 $* 都能访问全部参数,但行为有细微差别,Bash 手册页给出了明确解释:
$*:从 1 开始展开位置参数。当展开发生在双引号内时,它会合并成单个单词,各参数值之间用 IFS 特殊变量的首字符分隔。
$@:从 1 开始展开位置参数。在双引号内展开时,每个参数会被当作独立的单词。
所以,当你想安全地遍历所有参数时,"$@" 才是正确的打开方式。
获取参数数量
$# 给出传递给脚本的参数个数。一个很典型的应用场景是检查传入的参数是否符合预期:
if [ $# -eq 0 ]; then
echo "No arguments supplied"
fi
示例 1——遍历全部参数并检查是否为文件:
for item in "$@"; do
if [[ -f $item ]]; then
echo "$item is a file"
fi
done
示例 2——用索引循环做同样的事:
for ((i = 1; i <= $#; ++i)); do
item="${@:i:1}"
if [[ -f $item ]]; then
echo "$item is a file"
fi
done
23.5 在 Bash 中将字符串分割成数组
假如我们拿到一个字符串参数,希望按逗号拆分成数组:
my_param="foo,bar,bash"
用它来构造数组可以借助 IFS 和 read 一步到位:
IFS=',' read -r -a array <<< "$my_param"
这里 IFS(Internal Field Separator,内部字段分隔符)定义了哪些字符用来切分标记。操作完成后,可以这样访问单个元素:
echo "${array[0]}" # 输出:foo
遍历所有元素:
for element in "${array[@]}"; do
echo "$element"
done
如果需要同时获取索引和值:
for index in "${!array[@]}"; do
echo "$index ${array[$index]}"
done
至此,这些 Bash 参数处理技巧已经覆盖了绝大多数场景。多练几次,你自然就能根据实际需求灵活选择最合适的方案。