Linux技巧:Bash参数和参数扩展(Shell)
现在,很多 Linux® 和 UNIX® 系统上都有 bash shell,它是 Linux 上常见的默认 shell。通过本⽂,您将了解到如何在 bash 脚本中处理参数和选项,以及如何使⽤ shell 的参数扩展检查或修改参数。本⽂重点介绍 bash,⽂中的⽰例都是在以 bash 为 shell 的 Linux 系统上运⾏。但是,很多其他的 shell 中也有这些扩展,⽐如 ksh、ash 或 dash,您可以在其他 UNIX 系统或者甚⾄是 Cygwin 之类的环境中使⽤这些 shell 和扩展。早前的⼀篇⽂章 已经对本⽂中的构建⼯具进⾏了介绍。 本⽂中的某些材料摘录⾃ developerWorks 教程 ,该教程介绍了很多基本的脚本编程技术。
传递的参数
函数和 shell 脚本的妙处之⼀是,通过向单个函数或脚本传递参数 能够使它们表现出不同的⾏为。在本节中,您将了解到如何识别和使⽤传递的参数。
在函数或脚本中,您可以使⽤表 1 中列出的 bash 特殊变量来引⽤参数。您可以给这些变量附上 $ 符号的前缀,然后像引⽤其他 shell 变量那样引⽤它们。
表 1. 函数的 Shell 参数
参数⽬的
0, 1, 2, ...位置参数从参数 0 开始。参数 0 引⽤启动 bash 的程序的名称,如果函数在 shell 脚本中运⾏,则引⽤ shell 脚本的名称。有关该参数的其他信息,⽐如 bash 由 -c 参数启动,请参阅 bash ⼿册页⾯。由单引号或双引号包围的字符串被作为⼀个参数进⾏传递,传递时会去掉引号。 如果是双引号,则在调⽤函数之前将对 $HOME 之类的 shell 变量进⾏扩展。对于包含嵌⼊空⽩或其他字符(这些空⽩或字符可能对 shell 有特殊意义)的参数,需要使⽤单引号或双引号进⾏传递。
*位 置参数从参数 1 开始。如果在双引号中进⾏扩展,则扩展就是⼀个词,由IFS 特殊变量的第⼀个字符将参数分开,如果 IFS 为空,则没有间隔空格。IFS 的默认值是空⽩、制表符和换⾏符。如果没有设置 IFS,则使⽤空⽩作为分隔符(仅对默认 IFS ⽽⾔)。
@位置参数从参数 1 开始。如果在双引号中进⾏扩展,则每个参数都会成为⼀个词,因此 “$@” 与 “$1” “$2” 等效。如果参数有可能包含嵌⼊空⽩,那么您将需要使⽤这种形式。
#参数数量(不包含参数 0)。
注意:如果您拥有的参数多于 9 个,则不能使⽤ $10 来引⽤第⼗个参数。⾸先,您必须处理或保存第⼀个参数($1),然后使⽤ shift 命令删除参数 1 并将所有剩余的参数下移⼀位,因此 $10 就变成了 $9,依此类推。$# 的值将被更新以反映参数的剩余数量。在实践中,最常见的情况是将参数迭代到函数或 shell 脚本,或者迭代到命令替换使⽤ for 语句创建的列表,因此这个约束基本不成问题。
现在,您可以定义⼀个简单函数,其⽤途只是告诉您它所拥有的参数数量并显⽰这些参数,如清单 1 所⽰。
清单 1. 函数参数
[ian@pinguino ~]$ testfunc () { echo "$# parameters"; echo "$@"; }
[ian@pinguino ~]$ testfunc
0 parameters
[ian@pinguino ~]$ testfunc a b c
3 parameters
a b c
[ian@pinguino ~]$ testfunc a "b c"
2 parameters
a b c
Shell 脚本处理参数的⽅式与函数处理参数的⽅式相同。实际上,您会经常发现,脚本往往由很多⼩型的函数装配⽽成。清单 2 给出了⼀个 shell 脚本testfunc.sh,⽤于完成相同的简单任务,结果是要使⽤上⾯的⼀个输⼊来运⾏这个脚本。记住使⽤ chmod +x 将脚本标记为可执⾏。
清单 2. Shell 脚本参数
[ian@pinguino ~]$ cat testfunc.sh
#!/bin/bash
echo "$# parameters"
echo "$@";
[ian@pinguino ~]$ ./testfunc.sh a "b c"
2 parameters
a b c
在表 1 中您会发现,shell 可能将传递参数的列表引⽤为 $* 或 $@,⽽是否将这些表达式⽤引号引⽤将
影响它们的解释⽅式。对于上⾯的函数⽽⾔,使⽤$*、“$*”、$@ 或 “$@” 输出的结果差别不⼤,但是如果函数更复杂⼀些,就没有那么肯定了,当您希望分析参数或将⼀些参数传递给其他函数或脚本时,使⽤或不⽤引号的差别就很明显。 清单 3 给出了⼀个函数,⽤于打印参数的数量然后根据这四种可选⽅案打印参数。清单 4 给出了使⽤中的函数。IFS 默认变量使⽤⼀个空格作为它的第⼀个字符,因此清单 4 添加了⼀条竖线作为 IFS 变量的第⼀个字符,更加清楚地显⽰了在 “$*” 扩展中的何处使⽤这个字符。
清单 3. ⼀个探究参数处理差别的函数
[ian@pinguino ~]$ type testfunc2
testfunc2 is a function
testfunc2 ()
{
echo "$# parameters";
echo Using '$*';
for p in $*;
do
echo "[$p]";
done;
echo Using '"$*"';
for p in "$*";
do
echo "[$p]";
done;
echo Using '$@';
for p in $@;
do
echo "[$p]";
done;
echo Using '"$@"';
for p in "$@";
do
echo "[$p]";
done
}
清单 4. 使⽤ testfunc2 打印参数信息
[ian@pinguino ~]$ IFS="|${IFS}" testfunc2 abc "a bc" "1 2
> 3"
3 parameters
Using $*
[abc]
[a]
[bc]
[1]
[2]
[3]
Using "$*"
[abc|a bc|1 2
3]
Using $@
[abc]
[a]
[bc]
[1]
[2]
[3]
Using "$@"
[abc]
[a bc]
[1 2
3]
仔细研究⼆者的差别,尤其要注意加引号的形式和包含空⽩(如空格字符和换⾏符)的参数。在⼀个 [] 字符对中,注意:“$*” 扩展实际上是⼀个词。
选项和 getopts
传统的 UNIX 和 Linux 命令将⼀些传递的参数看作选项。过去,这些参数是单个的字符开关,与其他参数的区别在于拥有⼀个前导的连字符或负号。为⽅便起见,若⼲个选项可以合并到 ls -lrt 命令中,它提供了⼀个按修改时间(-t 选项)反向(-r 选项)排序的长(-l 选项)⽬录清单。
您可以对 shell 脚本使⽤同样的技术,getopts 内置命令可以简化您的任务。要查看此命令的⼯作原理,可以考虑清单 5 所⽰的⽰例脚本testopt.sh。
清单 5. testopt.sh 脚本
#!/bin/bash
echo "OPTIND starts at $OPTIND"
while getopts ":pq:" optname
do
case "$optname" in
"p")
echo "Option $optname is specified"
;;
"q")
echo "Option $optname has value $OPTARG"
;;
"?")
echo "Unknown option $OPTARG"
;;
":")
echo "No argument value for option $OPTARG"
;;
*)
# Should not occur
echo "Unknown error while processing options"
;;
esac
echo "OPTIND is now $OPTIND"
done
getopts 命令使⽤了两个预先确定的变量。OPTIND 变量开始被设为 1。之后它包含待处理的下⼀个参数
的索引。如果到⼀个选项,则 getopts 命令返回true,因此常见的选项处理范例使⽤带 case 语句的 while 循环,本例中就是如此。 getopts 的第⼀个参数是⼀列要识别的选项字母,在本例中是 p 和 r。选项字母后的冒号 (:) 表⽰该选项需要⼀个值;例如, -f 选项可能⽤于表⽰⽂件名, tar 命令中就是如此。此例中的前导冒号告诉 getopts 保持 静默(silent)并抑制正常的错误消息,因为此脚本将提供它⾃⼰的错误处理。
此例中的第⼆个参数 optname 是⼀个变量名,该变量将接收到选项的名称。如果预期某个选项应该拥有⼀个值,⽽且⽬前存在该值,则会把该值放⼊ OPTARG 变量中。在静默模式下,可能出现以下两种错误情况。
1. 如果发现不能识别的选项,则 optname 将包含⼀个 ? ⽽ OPTARG 将包含该未知选项。
2. 如果发现⼀个选项需要值,但是不到这个值,则 optname 将包含⼀个 : ⽽ OPTARG 将包含丢失参数的选项的名称。
如果不是在静默模式,则这些错误将导致⼀条诊断错误消息⽽ OPTARG 不会被设置。脚本可能在 optname 中使⽤ ? 或 : 值来检测错误(也可能处理错误)。
清单 6 给出了运⾏此简单脚本的两个⽰例。
清单 6. 运⾏ testopt.sh 脚本
[ian@pinguino ~]$ ./testopt.sh -p -q
OPTIND starts at 1
Option p is specified
OPTIND is now 2
No argument value for option q
OPTIND is now 3
[ian@pinguino ~]$ ./testopt.sh -p -q -r -s tuv
OPTIND starts at 1
Option p is specified
OPTIND is now 2
Option q has value -r
OPTIND is now 4
Unknown option s
OPTIND is now 5
如果您需要这样做,可以传递⼀组参数给 getopts 计算。如果您在脚本中已经使⽤⼀组参数调⽤了 getopts,现在要⽤另⼀组参数来调⽤它,则需要亲⾃将OPTIND 重置为 1。有关更多详细内容,请参阅 bash ⼿册或信息页⾯。
参数扩展
您已经了解了如何将参数传递给函数或脚本以及如何识别选项,现在开始处理选项和参数。如果在处理选项后可以知道留下了哪些参数,那应该是⼀种不错的事情。接下来您可能需要验证参数值,或者为丢失的参数指派默认值。本节将介绍⼀些 bash 中的参数扩展。当然,您仍然拥有 Linux 或 UNIX 命令(如 sed 或 awk)的全部功能来执⾏更复杂的⼯作,但是您也应该了解如何使⽤ shell 扩展。
我们开始使⽤上述的选项分析和参数分析函数来构建⼀个脚本。清单 7 中给出了 testargs.sh 脚本。
清单 7. testargs.sh 脚本
#!/bin/bash
showopts () {
while getopts ":pq:" optname
do
case "$optname" in
"p")
echo "Option $optname is specified"
;;
"q")
echo "Option $optname has value $OPTARG"
;
;
"?")
echo "Unknown option $OPTARG"
;;
":")
shell脚本返回执行结果echo "No argument value for option $OPTARG"
;;
*)
# Should not occur
echo "Unknown error while processing options"
;;
esac
done
return $OPTIND
}
showargs () {
for p in "$@"
do
echo "[$p]"
done
}
optinfo=$(showopts "$@")
argstart=$?
arginfo=$(showargs "${@:$argstart}")
echo "Arguments are:"
echo "$arginfo"
echo "Options are:"
echo "$optinfo"
尝试运⾏⼏次这个脚本,看看它如何运作,然后再对它进⾏详细考察。清单 8 给出了⼀些样例输出。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论