命令行参数
脚本还可以接受一些参数。向shell脚本传递数据的最基本方法是使用命令行参数,命令行参数就像是一些常规的Linux命令一样,例如head -20 file001.sh
,在一条命令上,head命令就能接收一个-20
的参数,用户自己定义的脚本,也可以接收一定的参数。
读取参数
第1个案例:读取1个参数
bash shell会将一些称为位置参数(positional parameter)的特殊变量分配给输入到命令行中的所有参数,这也包括shell所执行的脚本名称,位于参数变量是标准的数据,其中,$0
是程序名,$1
是第1个参数,$2
是第2个参数,直到第9个参数,即$9
。下面的案例是shell脚本中使用单个命令行参数的例子。
|
|
运行结果如下所示:
|
|
运行过程:第1次:factorial=1,number=1;第2次:n=2,f=2;第3次:n=3,f=6;第4次:n=4,f=24;第5次:n=5;f=120。
第2个案例:读取2个及以上参数
如果要读取更多的参数,需要将每个参数用空格分开,如下所示:
|
|
运行结果如下所示:
第3个案例,使用字符串作为变量
|
|
运行结果如下所示:
在这个案例中,使用了字符串作为脚本的参数,但是,如果参数中含有空格,则会出现问题,如下所示:
脚本只识别了Zhang,没有识别出完整的Zhang San,因为shell脚本会把空格分隔开的字符串当成两个参数,如果要使含有空格的字符串当成一个,需要添加引号,如下所示;
|
|
第4个案例:参数大于9个
如果脚本需要的命令行参数大于9个,此时需要在变量数字周围加上花括号,例如${``10``}
,如下所示:
|
|
结果如下所示:
第5个案例:读取脚本名
$0
参数的意义。
$0
参数用于获取shell在命令行启动的脚本,如下所示:
|
|
需要注意的是,如果使用完整路径运行脚本,则$0
就是完整脚本,如下所示:
|
|
basename
命令可以补充$0
参数的局限
如果要写一个根据脚本名来执行不同功能的脚本,则就需要注意把脚本的运行路径给剥离掉,shell中有一个命令可以考虑,即basename
命令,如下所示:
|
|
运行结果如下所示:
|
|
从结果可以看出来,使用了basename
命令后,即使运行脚本时输入全路径,$0
也只是脚本的名称。
相对路径与绝对路径的案例
|
|
运行结果如下所示:
|
|
结果解释:第一,cp test6.sh addem
复制test6.sh
脚本,新生成的文件命令为addem
;第二,chmod u+x addem
,将addem
文件权限更改为可执行;第三,ln -s test6.sh multem
,其中ln
是为文件创建链接,-s
表示软连接,可以理解为Linux的快捷方式;第四,当执行addem 2 5
时,执行total=$[ $1 + $2 ]
语句,如果是bash ./multem 2 5
,则执行total=$[ 41*$2 ]
语句。
测试参数
当脚本认为参数变量中会有数据,而实际上没有时,脚本就有可能会产生错误消息。通常要避免这种脚本的写法,在使用参数前一定要检查其中是否存在为数据,如下所示:
|
|
运行结果如下所示:
|
|
代码解释:在if
语句中使用了if [ -n "$1" ]
语句,这里的-n
是用于判断$1
中是否有数据。
特殊参数变量
在bash shell中有一些变量,它们会记录命令行参数。
参数统计基本用法
bash shell有一个特殊的变量,可以记录命令行中输入了多少个参数,其中这个特殊变量就是$#
,哦可以肝脚本中任何地方使用这个特殊的变量,如下所示:
|
|
运行结果如下所示:
|
|
参数统计案例
|
|
结果运行如下所示:
|
|
需要注意的是,如果认为$#
变量含有参数的总数,而变量$\{$#\}
代表了最后一个命令行参数变量,那就会出错,如下所示:
|
|
运行结果如下所示:
|
|
结果是19343,这说明,在花括号内不能使用美元符号,除非按照下面的方式进行修改,如下所示:
|
|
运行结果如下所示:
|
|
从结果来看,如果将$#
变量的值赋给了变量params
,然后也按特殊命令行参数变量的格式(即使用$/{!#/}
)使用了该变量,结果就能正常显示。但是,当脚本没有参数时,$#
的值为0,params变量的值也一样,但$\{!#\}
变量的返回值则是脚本名本身。
抓取所有的数据
有时候需要抓取命令行上提供的所有参数,这时候不需要先用$#
变量来判断命令行上有多少参数,然后再进行遍历,可以使用其他的特殊变量来解决这个问题,其中$*
和$@
变量可以访问所有的参数,这两个变量都能够在单个变量中存储所有的命令行参数。
$*
变量会将命令行上提供的所有参数当作一个单词保存,这个单词包含了命令行中出现的每一个参数,基本上$*
变量会将这些参数视为一个整体,而不是单独的个体。而$@
变量会将命令行上提供的所有参数当作同一字符串中多个独立的单词,这样可以遍历所有的参数值,得到每个参数,这通常使用for命令完成。如下所示:
|
|
运行结果如下所示:
|
|
从结果来看,$*
和$@
在形式上并没有区别。但是下面的例子则能看出区别:
|
|
运行结果如下所示:
|
|
从结果可以看出来,使用for遍历这两个特殊的变量时,$*
会将所有参数当成单个参数,而$@
变量会单独处理所有的参数。
移动变量
bash shell中的shift
命令可以操作命令行参数,shift
可以根据它们的相对位置来移动命令行参数。当使用该命令时,它会将每个参数变量向左移动一个位置。因此,变量$3
的值会移到$2
,变量$2
的值会移到$1
,而变量$1
的值则会被删除(因为$0
的值是程序名,不会改变)。这是遍历命令行参数的一个方法,当用户不矢科有多少个参数时,可以只操作第1个参数,移动参数,然后继续操作下一个参数。看一个案例:
|
|
运行结果如所示:
|
|
代码及结果解释: -n 测试来检查命令行参数$1
中是否有数据。如果不是0,就执行,一直执行到$1
参数为0为止。也可以为shift提供一个参数,指明要移动的位置数即可,如下所示:
|
|
运行结果如下所示:
|
|
从上述案例可知,通过使用shfit的参数可以跳过不想要的参数。
处理选项
选项指的是这样的一种情况,在命令后面的短横线的单个字母 ,例如 ls -a
,其中-a
就是选项。
查找选项
在提取每个单独参数时用case
语句来判断某个参数是否为选项。
|
|
运行结果如下所示:
|
|
case语句会检查每个参数是不是有效选项。如果是的话,就运行对应case语句中的命令,无论选项按什么顺序出现在命令行上,这种方法都适用,如下所示:
|
|
case语句在命令行参数中找到一个选项,就处理一个选项,如果命令行上的还提供了其他参数,就可以在case语句的通用情况处理部分中处理。
分离参数和选项
在shell脚本中同时使用选项有参数时,Linux处理这种问题的标准方式是用特殊字符,也就是双破折号(--
)将二者分开,该字符会告诉脚本何时选项结束以及普通参数何时开始。在双破折线之后,脚本可以将剩下的命令行参数当作参数,而不是选项,如下所示:
|
|
运行结果如下所示:
|
|
当脚本遇到双破折号时,它会停止处理选项,并将剩下的参数都当作命令行参数。
处理带值的选项
有些选项会带上额外的参数值,例如像这样的bash testing.sh -a test1 -b -c -d test2
,如果例行选项中带有额外的参数,脚本必须要能检测到,并且进行处理,如下所示:
|
|
运行结果如下所示:
|
|
代码及结果解释:在这案例中,case
语句定义了3个安要处理的选项,其中-b
选项还需要一个额外的参数值,由于要处理的参数是$1
,额外的参数值就应该位于$2
位置上(因为所有的参数在处理完后就会被移出),只要将参数值从$2
变量中提取出来就行,因为这个选项占了2个参数位,因此还需要shift
命令多移动一个位置。只用这些基本的特性,整个过程就能正常工作,不管按什么顺序放置选项(但要记住包含每个选项相应的选项参数)。如下所示:
|
|
此时,shell脚本就有了处理例行选项的基本能力,但还一些限制,例如,想要多个选项放一个参数时,就像ls -lh
这个命令这样,脚本就无法正常工作,如下所示:
|
|
此时需要getopt
命令。
使用getopt命令
getopt命令基本用法
getopt
命令是一个在处理命令行选项和参数时非常方便的工具,它能够识别命令行参数,从而在脚本中解析它们时更方便。getopt
命令可以接受一系列任意形式的命令行选项和参数,并自动将它们转换为适当的格式,它的使用方法为getopt optstring parameters
,其中,optstring
定义了命令行有效的选项字符,不这它义了哪些选项字母需要参数值。首先在optstring
中列出要在脚本中用到的每个命令行选项字母,然后在每个需要参数值的选项字母后面加一个冒号,getopt
命令会基于定义的optstring
解析提供的参数。如下所示:
|
|
optstring定义了4个有效选项字母,a、b、c和d。冒号(:
)被放到了字母b的后面,因为b选项需要一个参数值,当getopt
命令运行时,它会检查提供的参数列表(-a -b test1 -cd test2 test3),并基于提供的optstring
进行解析。此时,它会自动将-cd
选项分成两个单独的选项,并插入双破折线来分隔行中的额外参数,如果指定了一个不在optstring
中的选项,默认情况下,getopt
会产生一条错误消息,如下所示:
|
|
如果需要忽略这条错误消息,可以在命令后加-q
选项,如下所示:
|
|
注意:getopt命令选项必须出现在optstring之间。
在脚本中使用getopt
在脚本中使用getopt
来格式化脚本所携带的任何选项或参数时,方法就是使用getopt
命令生成的格式化后的版本替换已有的命令行选项的参数。这其中要用到set
命令。set
命令的选项之一就是双破折线(--
),它会将命令行参数值的成set命令的命令行值。该方法会将原始脚本的命令行参数传给getopt
命令,之后再将getopt
命令的输出传给set
命令,用getopt
格式化后的命令行参数来替换原始的命令行参数,看起来就像这样的set --$(getopt -q ab:cd "$@")
,此时,原始命令行参数变量的值会被getopt
命令的输出替换,而getopt
已经格式化好了命令行参数。看一个案例:
|
|
运行结果如下所示:
|
|
从运行结果来看,第一个与第二个没有问题,第三个出现了问题,在第三个运行过程中,参数选项其中人有"test2 test3"
,用了双引号,由于getopt
命令不擅长处理带空格和引号的参数值,它会将空格当作参数分隔符,而不是根据双引号将二者当作一个参数。此时就需要另外一个命令,即getopts
,这个命令是在getopt
后面加了一个s
。
使用getopts命令(注意后面加了s)
getopts
命令能够与已有的shell参数变量进行配合。每调用它时,它一次只处理命令行上检测到的一个参数,处理完所有的参数后,它会退回一个大于0的退出状态码,因此这个命令很适合解析命令行所有参数的循环。它的用法为getopts optstring variable
。optstring
中储存的是有效的选项字母,如果选项字母要求有个参数值,就要加一个冒号,要去年错误消息的话,可以在optstring
之前加一个冒号,getopts
命令将当前参数保存在命令行中定义的variable
中。getopts
命令会用到两个环境变量,如果选项需要跟一个参数值,OPTARG
环境变量会保存这个值,OPTIND
环境变量保存了参数列表中getopts
正在处理的参数位置。这样能够在处理完选项之后继续处理其他命令行参数。看一个案例:
|
|
运行结果如下所示:
|
|
while语句定义了getopts
命令,指明了要查找哪些命令选项,以及每次迭代中储存它们的变量名(opt
)。此时case
语句的用法有些不同,getopts命令解析命令行选项时会移除开头的单破拆线,所以在case定义中不用单破折线。getopts命令的参数值中可以包含空格,如下所示:
|
|
还可以将选项字母和参数值放在一起使用,而不用加空格,如下所示:
|
|
getopts
命令能够从-b
选项中正确解析出test1
值。除此之外,getopts
还能够将命令行上找到的所有未定义的选项统一输出成问号,如下所示:
|
|
optstring
中未定义的选项字母会以问号形式发给代码。getopts
命令知道何时停止处理选项,并将参数留给用户处理,在使用getopts
处理每个选项时,它会将OPTIND
环境变量值增一。在getopts
完成处理时,用户可以使用shift
命令和OPTIND
值来移动参数,如下所示:
|
|
运行结果如下所示:
|
|
选项标准化
在Linux系统中,一些字母选项已经拥有了某种程度的标准含义,因此在写bash shell脚本时,用户自己脚本中如果要使用一些选项参数的话,最好与Linux系统中的保持一致,一些常用的linux命令选项如下所示:
选项 | 描述 |
---|---|
-a | 显示所有对象 |
-c | 生成一个计数 |
-d | 指定一个目录 |
-e | 扩展一个对象 |
-f | 指定读入数据的文件 |
-h | 显示命令的帮助信息 |
-i | 忽略文本大小写 |
-l | 产生输出的长格式版本 |
-n | 使用非交互模式(批处理) |
-o | 将所有输出重定向到的 |
-q | 以安静模式运行 |
-r | 递归地处理目录和文件 |
-s | 以安静模式运行 |
-v | 生成详细输出 |
-x | 排除某个对象 |
-y | 对所有问题回答yes |
获得用户输入
虽然命令行选项和参数可以从脚本用户处获得输入,但还有一种情况下,需要读取用户的一些信息,例如在运行某个脚本时,需要用户输入自己的用户名。bash shell为此提供了read
命令。
read
的基本用法
read
命令从标准输入(键盘)或另一个文件描述符中接受输入,在收入输入后,read
命令会将数据放进一个变量,下面是read
命令的一个案例。
|
|
运行后结果如下所示:
|
|
代码及结果解释:生成提示的echo使用了-n
选项,该选项不会在字符串末尾输出换行符,允许脚本用户紧跟其后输入数据,而不是下一行。read
命令还包含了-p
选项,可以直接在read
命令行指定指示符,如下所示:
|
|
运行结果如下所示:
|
|
代码及结果解释,在第一个例子中当有名字输入时,read
命令会将姓和名保存在同一个变量中,read
命令会将提示符后输入的所有数据分配给单个变量,要么用户指定多个变量。输入的每个数据值都会分配给变量列表中的下一个变量,如果变量数量不够,剩下的数据就全部分配给最后一个变量,如下所示:
|
|
结果运行如下所示:
|
|
如果read
命令行中不指定变量,read
命令会将它收到的任何数据都放到特殊环境变量REPLY
中。
|
|
运行结果如下所示:
|
|
REPLY
环境变量会保存输入的所有数据,可以在shell脚本中像其他变量一样使用。
超时
在使用read
命令时需要注意,脚本很有可能会一直等着用户的输入,如果不管是否有数据输入,脚本都必须继续执行,可以使用-t
选项来指定个计时器,-t
选项指定了read
命令等待输入的秒数,当计时器过期后,read
命令会返回一个非零退出状态码。
|
|
运行结果如下所示:
|
|
代码及结果解释:如果计时器过期,read
命令会以非零退出状态码退出,可以使用如if-then
语句或while
循环这种标准的结构化语句所发生的具体情况,在这个例子中,if
语句不成立,shell会执行else部分的命令。也可以不进行诸,让read
命令来统计输入的字符数,当输入的字符数达到预设的字符数时,就自动退出,将输入的数据赋给变量,如下所示:
|
|
运行结果如下所示:
|
|
在这个案例中,将-n选项
和值1
一起使用,告诉read
命令在接受单个字符后退出,只要按下单个字符回答后,read
命令就会接受输入并将它传给变量,无需按回车键。
隐藏方式读取
有时候需要从脚本用户处得到输入,在屏幕上显示输入信息。但有种例外,就是输入密码,需要隐藏输入的信息。在read
命令使用时,使用-s
选项可以避免在read
命令中输入的数据出现在显示器上(其实数据是会显示的,只是read
命令将文本的颜色设成了跟背景色一样),下面是一个案例。
|
|
运行结果如下所示:
|
|
输入提示符输入的数据不会出现在屏幕上,但会赋值给变量,以便在脚本中使用。
从文件中读取数据
read
可以用于读取文件中的数据,每次调用read
命令时,它就会从文件中读取一行文本。当文件中再没有内容时,read
命令就会退出,并返回非零退出状态码。通常使用cat
命令将数据中的文件传给含有read
命令的while
命令,看一个案例。
|
|
运行结果如下所示:
|
|