1命令行参数
1.1读取参数
bash shell会将一些称为位置参数(positional parameter)的特殊变量分配给输入到命令行中的所有参数。这也包括shell所执行的脚本名称。位置参数变量是标准的数字:$0是程序名,$1是第一个参数,$2是第二个参数,依次类推,直到第九个参数$9。
例子:计算阶乘
$ vim test1.sh #!/bin/bash # using one command line parameter factorial=1 for (( number = 1; number <= $1 ; number++ )) do factorial=$[ $factorial * $number ] done echo The factorial of $1 is $factorial $ ./test1.sh 5 The factorial of 5 is 120
例子:参数是字符串的情况
$ cat test3.sh #!/bin/bash # testing string parameters echo "Hello $1, glad to meet you." $ ./test3.sh Rich Hello Rich, glad to meet you.
如果脚本需要的命令行参数不止9个的话。可以在第9个变量之后,在变量数字周围加上花括号,比如${10}。
1.2读取脚本名
以用$0参数获取shell在命令行启动的脚本名。
$ cat test5.sh #!/bin/bash # Testing the $0 parameter echo The zero parameter is set to: $0 $bash test5.sh The zero parameter is set to: test5.sh
但是这里存在一个潜在的问题。如果使用另一个命令来运行shell脚本,命令会和脚本名混在一起,出现在$0参数中。比如:
$ ./test5.sh The zero parameter is set to: ./test5.sh $ bash /home/Christine/test5.sh The zero parameter is set to: /home/Christine/test5.sh
这个时候basename命令可以帮助我们,basename命令会返回不包含路径的脚本名。
$ cat test5b.sh #!/bin/bash # Using basename with the $0 parameter name=$(basename $0) echo echo The script name is: $name $ bash /home/Christine/test5b.sh The script name is: test5b.sh $ ./test5b.sh The script name is: test5b.sh
可以用这种方法来编写基于脚本名执行不同功能的脚本。这里有个简单的例子:
$ cat test6.sh #!/bin/bash name=$(basename $0) #判断参数是否为空,如果为空的话(就是执行脚本时少带了参数),后面的脚本会报错 if [ -e $1 ] then echo "\$1 is empty" exit 1 elif [ -e $2 ] then echo "\$2 is empty" exit 1 fi if [ $name = "addem" ] then total=$[ $1 + $2 ] elif [ $name = "multem" ] then total=$[ $1 * $2 ] fi echo The calculated value is $total 开始测试: $ cp test6.sh addem $ chmod u+x addem $ ln -s test6.sh multem $ ls -l *em -rwxrw-r--. 1 Christine Christine 224 Jun 30 23:50 addem lrwxrwxrwx. 1 Christine Christine 8 Jun 30 23:50 multem -> test6.sh $ ./addem 2 5 The calculated value is 7 $ ./multem 2 5 The calculated value is 10
本例从test6.sh脚本中创建了两个不同的文件名:一个通过复制文件创建(addem),另一个通过链接创建(multem)。在两种情况下都会先获得脚本的基本名称,然后根据该值执行相应的功能。
2特殊参数变量
2.1参数统计
特殊变量$#:含有脚本运行时携带的命令行参数的个数
$ cat test8.sh #!/bin/bash # getting the number of parameters echo There were $# parameters supplied. $ ./test8.sh There were 0 parameters supplied. $ ./test8.sh 1 2 3 4 5 There were 5 parameters supplied. $ ./test8.sh 1 2 3 4 5 6 7 8 9 10 There were 10 parameters supplied. $ ./test8.sh "Rich Blum" There were 1 parameters supplied.
如果要直接获取最后一个参数的值时,不应该用${$#},而是应该用${!#}:
$ cat test10.sh #!/bin/bash # Grabbing the last parameter params=$# echo echo The last parameter is $params echo The last parameter is ${!#} $ bash test10.sh 1 2 3 4 5 The last parameter is 5 The last parameter is 5 $ bash test10.sh The last parameter is 0 The last parameter is test10.sh
这里注意参数变量个数时不包括$0的,所以最后一个测试实例中第一行是0,而$0是程序名,所以是test10.sh
2.2抓取所有的数据
$*和$@变量可以用来轻松访问所有的参数。这两个变量都能够在单个变量中存储所有的命令行参数。
$*变量会将命令行上提供的所有参数当作一个单词保存,这个单词包含了命令行中出现的每一个参数值;
$@变量会将命令行上提供的所有参数当作同一字符串中的多个独立的单词。
例子:
$ cat test10.sh #!/bin/bash # Grabbing the last parameter params=$# echo echo The last parameter is $params echo The last parameter is ${!#} $ bash test10.sh 1 2 3 4 5 The last parameter is 5 The last parameter is 5 $ bash test10.sh The last parameter is 0 The last parameter is test10.sh
3移动变量
shift命令能够用来操作命令行参数。它会根据相对位置来移动命令行参数。在使用shift命令时,默认情况下它会将每个参数变量向左移动一个位置。所以,变量$3的值会移到$2中,变量$2的值会移到$1中,而变量$1的值则会被删除(注意,变量$0的值,也就是程序名,不会改变)。
$ cat test13.sh #!/bin/bash count=1 while [ -n "$1" ] do echo "Parameter #$count = $1" count=$[ $count + 1 ] shift done $ ./test13.sh rich barbara katie jessica Parameter #1 = rich Parameter #2 = barbara Parameter #3 = katie Parameter #4 = jessica
另外,你也可以一次性移动多个位置,只需要给shift命令提供一个参数,指明要移动的位置数就行了。
$ cat test14.sh #!/bin/bash echo "The original parameters: $*" shift 2 echo "Here's the new first parameter: $1" $ ./test14.sh 1 2 3 4 5 The original parameters: 1 2 3 4 5 Here's the new first parameter: 3
4处理选项
4.1查找选项
1. 处理简单选项
$ cat test15.sh #!/bin/bash # extracting command line options as parameters while [ -n "$1" ] do case "$1" in -a) echo "Found the -a option" ;; -b) echo "Found the -b option" ;; -c) echo "Found the -c option" ;; *) echo "$1 is not an option" ;; esac shift done $ ./test15.sh -a -b -c -d Found the -a option Found the -b option Found the -c option -d is not an option
2. 分离参数和选项
双破折线(--):表明选项列表结束。
$ cat test16.sh #!/bin/bash while [ -n "$1" ] do case "$1" in -a) echo "Found the -a option" ;; -b) echo "Found the -b option";; -c) echo "Found the -c option" ;; --) shift break ;; *) echo "$1 is not an option";; esac shift done count=1 for param in $@ do echo "Parameter #$count: $param" count=$[ $count + 1 ] done $ ./test16.sh -c -a -b test1 test2 test3 Found the -c option Found the -a option Found the -b option test1 is not an option test2 is not an option test3 is not an option $ ./test16.sh -c -a -b -- test1 test2 test3 Found the -c option Found the -a option Found the -b option Parameter #1: test1 Parameter #2: test2 Parameter #3: test3
3. 处理带值的选项
$ cat test17.sh #!/bin/bash while [ -n "$1" ] do case "$1" in -a) echo "Found the -a option";; -b) param="$2" echo "Found the -b option, with parameter value $param" shift ;; -c) echo "Found the -c option";; --) shift break ;; *) echo "$1 is not an option";; esac shift done count=1 for param in "$@" do echo "Parameter #$count: $param" count=$[ $count + 1 ] done $ ./test17.sh -a -b test1 -d Found the -a option Found the -b option, with parameter value test1 -d is not an option
但是这种脚本遇到$ ./test17.sh -ac的命令九九没法执行了。
4.2使用getopt命令
1. 命令的格式
getopt optstring parameters
下面是个getopt如何工作的简单例子:
$ getopt ab:cd -a -b test1 -cd test2 test3 -a -b test1 -c -d -- test2 test3
optstring定义了四个有效选项字母:a、b、c和d。冒号(:)被放在了字母b后面,因为b选项需要一个参数值。并且它会自动将-cd选项分成两个单独的选项,并插入双破折线来分隔行中的额外参数。
如果指定了一个不在optstring中的选项,默认情况下,getopt命令会产生一条错误消息。
$ getopt ab:cd -a -b test1 -cde test2 test3 getopt: invalid option -- e -a -b test1 -c -d -- test2 test3
2. 在脚本中使用getopt
如果想忽略这条错误消息,可以在命令后加-q选项。
$ getopt -q ab:cd -a -b test1 -cde test2 test3 -a -b 'test1' -c -d -- 'test2' 'test3'
例子:
$ cat test18.sh #!/bin/bash set -- $(getopt -q ab:cd "$@") while [ -n "$1" ] do case "$1" in -a) echo "Found the -a option" ;; -b) param="$2" echo "Found the -b option, with parameter value $param" shift ;; -c) echo "Found the -c option" ;; --) shift break ;; *) echo "$1 is not an option";; esac shift done count=1 for param in "$@" do echo "Parameter #$count: $param" count=$[ $count + 1 ] done
在getopt命令中仍然隐藏着一个小问题。看看这个例子:
$ ./test18.sh -a -b test1 -cd "test2 test3" test4 Found the -a option Found the -b option, with parameter value 'test1' Found the -c option Parameter #1: 'test2 Parameter #2: test3' Parameter #3: 'test4'
4.3使用更高级的getopts
getopts命令的格式如下:
getopts optstring variable
optstring值类似于getopt命令中的那个。有效的选项字母都会列在optstring中,如果选项字母要求有个参数值,就加一个冒号。要去掉错误消息的话,可以在optstring之前加一个冒号。getopts命令将当前参数保存在命令行中定义的variable中。
getopts命令会用到两个环境变量。如果选项需要跟一个参数值,OPTARG环境变量就会保存这个值。OPTIND环境变量保存了参数列表中getopts正在处理的参数位置。
$ cat test19.sh #!/bin/bash # simple demonstration of the getopts command while getopts :ab:c opt do case "$opt" in a) echo "Found the -a option" ;; b) echo "Found the -b option, with value $OPTARG";; c) echo "Found the -c option" ;; *) echo "Unknown option: $opt";; esac done $ ./test19.sh -ab test1 -c Found the -a option Found the -b option, with value test1 Found the -c option
while语句定义了getopts命令,指明了要查找哪些命令行选项,以及每次迭代中存储它们的变量(opt)。getopts命令解析命令行选项时会移除开头的单破折线,所以在case定义中不用单破折线。
getopts命令有几个好用的功能。对新手来说,可以在参数值中包含空格。
$ ./test19.sh -b "test1 test2" -a
Found the -b option, with value test1 test2
Found the -a option
另一个好用的功能是将选项字母和参数值放在一起使用,而不用加空格。
$ ./test19.sh -abtest1 Found the -a option Found the -b option, with value test1
getopts命令能够从-b选项中正确解析出test1值。除此之外,getopts还能够将命令行上
找到的所有未定义的选项统一输出成问号。
$ ./test19.sh -d Unknown option: ? $ ./test19.sh -acde Found the -a option Found the -c option Unknown option: ? Unknown option: ?
完整的例子:
$ cat test20.sh #!/bin/bash # Processing options & parameters with getopts while getopts :ab:cd opt do case "$opt" in a) echo "Found the -a option" ;; b) echo "Found the -b option, with value $OPTARG" ;; c) echo "Found the -c option" ;; d) echo "Found the -d option" ;; *) echo "Unknown option: $opt" ;; esac done shift $[ $OPTIND - 1 ] count=1 for param in "$@" do echo "Parameter $count: $param" count=$[ $count + 1 ] done $ ./test20.sh -a -b test1 -d test2 test3 test4 Found the -a option Found the -b option, with value test1 Found the -d option Parameter 1: test2 Parameter 2: test3 Parameter 3: test4
5将选项标准化
选 项 |
描 述 |
-a |
显示所有对象 |
-c |
生成一个计数 |
-d |
指定一个目录 |
-e |
扩展一个对象 |
-f |
指定读入数据的文件 |
-h |
显示命令的帮助信息 |
-i |
忽略文本大小写 |
-l |
产生输出的长格式版本 |
-n |
使用非交互模式(批处理) |
-o |
将所有输出重定向到的指定的输出文件 |
-q |
以安静模式运行 |
-r |
递归地处理目录和文件 |
-s |
以安静模式运行 |
-v |
生成详细输出 |
-x |
排除某个对象 |
-y |
对所有问题回答yes |
6获得用户输入
6.1基本的读取
read命令从标准输入(键盘)或另一个文件描述符中接受输入。在收到输入后,read命令会将数据放进一个变量。下面是read命令的几种简单用法。
第一种:
$ cat test22.sh #!/bin/bash # testing the read -p option read -p "Please enter your age: " age days=$[ $age * 365 ] echo "That makes you over $days days old! " $ ./test22.sh Please enter your age: 10 That makes you over 3650 days old!
第二种:
$ cat test23.sh #!/bin/bash # entering multiple variables read -p "Enter your name: " first last echo "Checking data for $last, $first…" $ ./test23.sh Enter your name: Rich Blum Checking data for Blum, Rich...
第三种:
也可以在read命令行中不指定变量。如果是这样,read命令会将它收到的任何数据都放进特殊环境变量REPLY中。
$ cat test24.sh #!/bin/bash # Testing the REPLY Environment variable read -p "Enter your name: " echo "Hello $REPLY, welcome to my program. " $ ./test24.sh Enter your name: Christine Hello Christine, welcome to my program.
REPLY环境变量会保存输入的所有数据,可以在shell脚本中像其他变量一样使用。
6.2 超时
使用read命令时要当心。脚本很可能会一直苦等着脚本用户的输入。如果不管是否有数据输入,脚本都必须继续执行,你可以用-t选项来指定一个计时器。-t选项指定了read命令等待输入的秒数。当计时器过期后,read命令会返回一个非零退出状态码。
$ cat test25.sh #!/bin/bash # timing the data entry if read -t 5 -p "Please enter your name: " name then echo "Hello $name, welcome to my script" else echo echo "Sorry, too slow! " fi $ ./test25.sh Please enter your name: Rich Hello Rich, welcome to my script $ ./test25.sh Please enter your name: Sorry, too slow!
如果计时器过期,read命令会以非零退出状态码退出,可以使用如if-then语句或while循环这种标准的结构化语句来理清所发生的具体情况。
也可以不对输入过程计时,而是让read命令来统计输入的字符数。当输入的字符达到预设的字符数时,就自动退出,将输入的数据赋给变量。
$ cat test26.sh #!/bin/bash # getting just one character of input read -n1 -p "Do you want to continue [Y/N]? " answer case $answer in Y | y) echo "fine, continue on…";; N | n) echo "OK, goodbye" exit;; esac echo "This is the end of the script" $ ./test26.sh Do you want to continue [Y/N]? Y fine, continue on… This is the end of the script $ ./test26.sh Do you want to continue [Y/N]? n OK, goodbye
本例中将-n选项和值1一起使用,告诉read命令在接受单个字符后退出。只要按下单个字符回答后,read命令就会接受输入并将它传给变量,无需按回车键。
6.3隐藏方式读取
有时你需要从脚本用户处得到输入,但又在屏幕上显示输入信息。其中典型的例子就是输入的密码,但除此之外还有很多其他需要隐藏的数据类型。
s选项可以避免在read命令中输入的数据出现在显示器上(实际上,数据会被显示,只是 read命令会将文本颜色设成跟背景色一样)。这里有个在脚本中使用-s选项的例子:
$ cat test27.sh #!/bin/bash # hiding input data from the monitor read -s -p "Enter your password: " pass echo echo "Is your password really $pass? " $ ./test27.sh Enter your password: Is your password really 123456?
6.4从文件中读取
可以用read命令来读取Linux系统上文件里保存的数据。每次调用read命令,它都会从文件中读取一行文本。当文件中再没有内容时,read命令会退出并返回非零退出状态码。
$ cat test28.sh #!/bin/bash # reading data from a file file="state" count=1 cat $file | while read line do echo "Line $count: $line" count=$[ $count + 1] done echo "Finished processing the file" $ cat state The quick brown dog jumps over the lazy fox. This is a test, this is only a test. O Romeo, Romeo! Wherefore art thou Romeo? $ ./test28.sh Line 1: The quick brown dog jumps over the lazy fox. Line 2: This is a test, this is only a test. Line 3: O Romeo, Romeo! Wherefore art thou Romeo? Finished processing the file
while循环会持续通过read命令处理文件中的行,直到read命令以非零退出状态码退出。