wangsx@SC-201708020022:~/tmp$ cat test13.sh
#!/bin/bash
# demonstrating the shift command
count=1
while [ -n "$1" ]
echo "Parameter #$count = $1"
count=$[ $count + 1 ]
shift
wangsx@SC-201708020022:~/tmp$ ./test13.sh rich barbara katie jessica
Parameter #1 = rich
Parameter #2 = barbara
Parameter #3 = katie
Parameter #4 = jessica
使用shift命令时需要注意,一旦参数被移除,它的值就被丢弃了 ,无法再恢复。
shift n
中n
可以指定移动多个位置。
熟悉Linux的朋友必然见过不少同时提供参数和选项的bash命令。选项是跟在但破折线后面的单个字母,它能改变命令的行为。下面介绍3中再脚本中处理选项的方法。
只要我们愿意,我们可以像处理命令行参数一样处理命令行选项。
处理简单选项
前面一节最后我们用shift
命令来依次处理脚本携带的命令行参数。我们也可以用同样的方法来处理命令行选项。
在提取每个单独参数时,用case
语句来判断某个参数是否为选项。
wangsx@SC-201708020022:~/tmp$ cat test15.sh
#!/bin/bash
# extracting command line option as parameter
while [ -n "$1" ]
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";;
shift
wangsx@SC-201708020022:~/tmp$ ./test15.sh -a -b -c -d
Found the -a option
Found the -b option
Found the -c option
-d is not an option
case
语句在命令行参数中找到一个选项,就处理一个选项。如果命令行上还提供了其他参数,你可以在case
语句的通用情况处理部分中处理。
分离参数与选项
shell脚本通常使用选项和参数,Linux中处理这个问题的标准方法是用特殊字符来将二者分开,该字符会告诉脚本何时选项结束以及普通参数何时开始。
这个所谓的特殊字符就是--
双破折号。
要检查双破折号,在case
语句中加一项就行了。
wangsx@SC-201708020022:~/tmp$ cat test16.sh
#!/bin/bash
# extracting options and paramters
while [ -n "$1" ]
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";;
shift
count=1
for param in $@
echo "Parameter #$count: $param"
count=$[ $count + 1 ]
当遇到双破折号时,先把它移除掉,然后跳出循环,这样shell就会把后面的参数当参数而不是选项处理了。
先用一组普通的选项和参数来运行测试脚本:
wangsx@SC-201708020022:~/tmp$ ./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
现在加上双破折号,进行测试:
wangsx@SC-201708020022:~/tmp$ ./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
可以看到,当脚本遇到双破折号时,它会停止处理选项,并将剩下的参数都当做命令处理。
这样如果顺序填写选项和参数的话,显然没什么问题。但是如果乱序写呢?很显然选项和参数对应不起来。如何解决?
处理带值的选项
有些选项会带上一个额外的参数值,类似下面:
./testing.sh -a test1 -b -c -d test2
下面看看怎么正确处理。
wangsx@SC-201708020022:~/tmp$ cat test17.sh
#!/bin/bash
# extracting command line options and values
while [ -n "$1" ]
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";;
shift
count=1
for param in "$@"
echo "Paramter #$count: $param"
count=$[ $count + 1 ]
wangsx@SC-201708020022:~/tmp$ ./test17.sh -a -b test1 -d
Found the -a option
Found the -b option, with parameter value test1
-d is not an option
本例中,定义了3个要处理的选项,-b
还带一个额外参数。因为处理的选项是$1
,所以额外参数位于$2
,另外因为加了额外参数,所以找到后应该用shift
把它移除(这个选项占了两个位置,需要多移动一个)。
这样,我们可以根据需求进行类似的设定了。不管什么顺序放置选项都可以正常工作。
wangsx@SC-201708020022:~/tmp$ ./test17.sh -b test1 -a -d
Found the -b option, with parameter value test1
Found the -a option
-d is not an option
不过,这里还有一些限制。如果我们想把多个选项放在一起,这样就行不通啦~
wangsx@SC-201708020022:~/tmp$ ./test17.sh -ac
-ac is not an option
而这种功能是Linux常见的喔。那究竟怎么合并选项呢?幸好还有一种处理方法可以帮忙。
使用getopt命令
getopt
命令是一个处理命令行选项和参数时非常方便的工具。它能够识别命令行参数,从而更方便地进行解析。
命令的格式
getopt optstring parameters
optstring
是这个过程的关键所在。它定义了命令行有效的选项字母,还定义了哪些字母需要带参数。
首先,在optstring
中列出你要在脚本中用到的每个命令行选项字母。然后,在每个需要参数值的选项字母后加一个冒号。
getopt
的高级版本叫getoptions
。需要注意区分
下面看看getopt
如何工作的:
wangsx@SC-201708020022:~/tmp$ getopt ab:cd -a -b test1 -cd test2 test3
-a -b test1 -c -d -- test2 test3
运行完后看到结果感觉自己晕乎乎的,让我们一起来看看解释:
optstring
定义了四个有效选项字母:a,b,c,d。(嗯,对的,这个没问题)。冒号被放在字母b后面,说明b选项需要一个参数。(这样啊)。当getopt
命令运行时,它会检查参数列表(就时getopt命令后面跟的),并基于提供的optstring
进行解析。值得注意,它会自动把-cd
选项分成两个独立的选项,并插入双破折号来分隔行中的额外参数。
如果指定的选项不在optstring
中,会报错。-q
选项可以忽略掉它(注意放在optstring
之前,因为是命令本身的选项嘛)。
wangsx@SC-201708020022:~/tmp$ getopt ab:cd -a -b test1 -cde test2 test3
getopt:无效选项 -- e
-a -b test1 -c -d -- test2 test3
wangsx@SC-201708020022:~/tmp$ getopt -q ab:cd -a -b test1 -cde test2 test3
-a -b 'test1' -c -d -- 'test2' 'test3'
在脚本中用getopt
用法稍微有点复杂,方法是用getopt
命令生成的格式化后的版本替换已有的命令行选项和参数。用set
可以做到。
set -- $(getopt -q ab:cd "$@)
set
命令的选项之一是--
,它会将命令行参数替换成set
命令的命令行值。
该方法会将原始脚本的命令行参数传给getopt
命令,之后将getopt
命令的输出传给set
命令,用getopt
格式化的命令行参数来替换原始的命令行参数。
现在写下处理命令行参数的脚本吧:
wangsx@SC-201708020022:~/tmp$ cat test18.sh
#!/bin/bash
# Extract command line options & values with getopt
set -- $(getopt -q ab:cd "$@")
while [ -n "$1" ]
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"
shift
count=1
for param in "$@"
echo "Parameter #$count: $param"
count=$[ $count + 1 ]
可以看到它跟test17.sh
不同的地方是加入了getopt
命令来帮助格式化命令行参数。
下面测试发现新加的功能实现了,之前的也没问题。
wangsx@SC-201708020022:~/tmp$ ./test18.sh -ac
Found the -a option
Found the -c option
wangsx@SC-201708020022:~/tmp$ ./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
-d is not an option
Parameter #1: 'test2'
Parameter #2: 'test3'
Parameter #3: 'test4'
相当不错啦。不过getopt
命令隐藏一个问题。
wangsx@SC-201708020022:~/tmp$ ./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
-d is not an option
Parameter #1: 'test2
Parameter #2: test3'
Parameter #3: 'test4'
getopt
命令并不擅长处理带空格和引号的参数值。它会将空格当作参数分隔符,而不是根据双引号将两者当作一个参数。
幸而还有办法能够解决这个问题。
使用更高级的getopts
getopts
命令内建于bash shell。它比getopt
多一些扩展功能。
getopts
命令格式如下:
getopts optstring variable
optstring
值类似于getopt
命令中的那个。要去掉错误信息的话,可以在optstring
之前加一个冒号。getopts
命令将当前参数保存在命令行中定义的variable
中。
该命令会用到两个环境变量。如果选项需要跟一个参数值,OPTARG
环境变量就会保存这个值。OPTIND
环境变量保存了参数列表中getopts
正在处理的参数位置。这样你就能在处理选项之后继续处理其他命令行参数了。
空说无益,还是来练练。
wangsx@SC-201708020022:~/tmp$ cat test19.sh
#!/bin/bash
# Simple demonstration of the getopts command
while getopts 🆎c opt
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";;
wangsx@SC-201708020022:~/tmp$ ./test19.sh -ab test1 -c
Found the -a option
Found the -b option, with value test1
Found the -c option
while
语句定义了getopts
命令,指明了要查找哪些命令行选项,以及每次迭代中存储它们的变量名(opt
)。
getopts
运行时,它一次只处理命令行上检测到的一个参数。处理完所有参数后,它会退出并返回一个大于0的退出状态码。这让它非常适合用于解析命令行所有的参数的循环中。
这里我们可以已经注意到了例子中的case
用法和之前不同。getopts
命令解析命令行选项时会移除开头的单破折号,所以在case
定义中不用单破折号了。
上一小节末尾我们遇到的问题可以很好的解决了:
wangsx@SC-201708020022:~/tmp$ ./test19.sh -ab "test1 test2" -c
Found the -a option
Found the -b option, with value test1 test2
Found the -c option
另一个好用的功能是能够将选项字母和参数值放在一起,而且不用加空格。
wangsx@SC-201708020022:~/tmp$ ./test19.sh -abtest1
Found the -a option
Found the -b option, with value test1
除此之外,getopts
还能够将命令行上找到的所有未定义的选项统一输出成问号。
wangsx@SC-201708020022:~/tmp$ ./test19.sh -d
Unknown option: ?
wangsx@SC-201708020022:~/tmp$ ./test19.sh -acde
Found the -a option
Found the -c option
Unknown option: ?
Unknown option: ?
getopts
命令知道何时停止处理选项,并将参数留给你处理。在getopts
处理每一个选项时,它会将OPTIND
环境变量值增一。在getopts
完成处理后,你可以使用shift
命令和OPTIND
值来移动参数。
wangsx@SC-201708020022:~/tmp$ cat test20.sh
#!/bin/bash
# Processing options & parameters with getopts
while getopts 🆎cd opt
case "$opt" in
a) echo "Found the -a option" ;;
b) echo "Found the -a option, with value $OPTARG" ;;
c) echo "Found the -c option" ;;
d) echo "Found the -d option" ;;
*) echo "Unknown option: $opt" ;;
shift $[ $OPTIND -1 ]
count=1
for param in "$@"
echo "Parameter $count: $param"
count=$[ $count + 1 ]
wangsx@SC-201708020022:~/tmp$ ./test20.sh -a -b test1 -d test2 test3 test4
Found the -a option
Found the -a option, with value test1
Found the -d option
Parameter 1: test2
Parameter 2: test3
Parameter 3: test4
这里shift $[ $OPTIND -1 ]
需要解释以下:前面提到OPTIND
在getopts
每次处理掉一个参数后会加1。比如./test20.sh -a -b test1 -d test2 test3 test4
前面键入了4个参数,选项处理完成后OPTIND
的值为5。它会指向$5
,即第5个参数,后面为了值剩下命令行参数,所以去掉所有的选项(及带的参数),所以用shift $[ $OPTIND - 1]
命令。
将选项标准化
有些字母在Linux世界里已经拥有了某种程度的标准含义。如果我们能在shellji奥本中支持这些选项,脚本看起来会更加友好。
下面表格列出一些命令行选项的常用含义
有时脚本地交互性还需要更强一些。比如你想要在脚本运行时问个问题,并等待运行脚本地人来回答。bash shell为此提供了read命令。
基本的读入
read
命令从标准输入(键盘)或另一个文件描述符中接受输入。在收到输入后,read
命令会将数据放进一个变量。
简单用法如下:
wangsx@SC-201708020022:~/tmp$ cat test21.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!"
wangsx@SC-201708020022:~/tmp$ ./test21.sh
Please enter your age: 23
That makes you over 8395 days old!
read
命令会将提示符后输入的所有数据分配给单个变量,要么我们需要指定多个变量。当变量数量不够时,剩下的数据就全部分配给最后一个变量。
wangsx@SC-201708020022:~/tmp$ cat test22.sh
#!/bin/bash
# entering multiple variable
read -p "Enter your name: " first last
echo "Checking data for $last, $first..."
wangsx@SC-201708020022:~/tmp$ ./test22.sh
Enter your name: shixiang wang
Checking data for wang, shixiang...
wangsx@SC-201708020022:~/tmp$ ./test22.sh
Enter your name: shixiang wang hhhhh
Checking data for wang hhhhh, shixiang...
也可以在read
命令行中不指定变量。如果这样的话,read
命令会将它收到的任何数据都放进特殊环境变量REPLY
中。我们直接可以使用它。
脚本很可能一直苦苦等待脚本用户的输入。如果不管数据是否输入,脚本都执行的话,我们可以用-t
选项设定一个计时器。-t
指定read
命令等待的秒数,计数完成后,read
命令会返回一个非零退出状态码。
wangsx@SC-201708020022:~/tmp$ cat test23.sh
#!/bin/bash
# timing the data entry
if read -t 5 -p "Please enter your name: " name
echo "Hello $name, welcome to my script"
echo "Sorry, too slow!"
wangsx@SC-201708020022:~/tmp$ ./test23.sh
Please enter your name: shixiang wang
Hello shixiang wang, welcome to my script
wangsx@SC-201708020022:~/tmp$ ./test23.sh # 这里输入后等以下 不要输入
Please enter your name:
Sorry, too slow!
也可以不对输入过程计时,而时让read
命令来统计输入的字符数。当输入的字符数达到预设的字符数时,就自动退出,将输入的数据赋值给变量。
wangsx@SC-201708020022:~/tmp$ cat test24.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
echo "fine, continue on...";;
N | n ) echo
echo OK, goodbye
exit;;
echo "This is the end of the script."
wangsx@SC-201708020022:~/tmp$ ./test24.sh
Do you want to continue [Y/N]? Y
fine, continue on...
This is the end of the script.
wangsx@SC-201708020022:~/tmp$ ./test24.sh
Do you want to continue [Y/N]? n
OK, goodbye
隐藏方式读取
这种方式输入密码的时候有用。
-s
选项可以避免在read
命令输入的数据出现在显示器上(实际上,数据会被显示,只是read
命令会将文本颜色设成跟背景色一样)。
wangsx@SC-201708020022:~/tmp$ cat test25.sh
#!/bin/bash
# hiding input data from the monitor
read -s -p "Enter your password: " pass
echo "Is your password really $pass?"
wangsx@SC-201708020022:~/tmp$ ./test25.sh
Enter your password:
Is your password really what?
wangsx@SC-201708020022:~/tmp$ ./test25.sh
Enter your password:
Is your password really shixiang?
从文件中读取
当然,read
也可以从系统文件中读取数据。每次调用read
命令,它都会读取一行文本。当读完后,read
命令会退出并返回非零退出状态码。
最常见的方法时对文本使用cat
命令,将结果通过管道直接传给含read
命令的while
命令。
wangsx@SC-201708020022:~/tmp$ cat test26.sh
#!/bin/bash
# reading data from a file
count=1
cat test | while read line
echo "Line $count: $line"
count=$[ $count + 1 ]
echo "Finished processing the file"
wangsx@SC-201708020022:~/tmp$ cat test
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?
wangsx@SC-201708020022:~/tmp$ ./test26.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
写shell脚本的基本内容大体已经整完了,我自己也是边看边想边码过来的。shell博大精深,更多高级内容有待继续学习整理。码字实属不易,觉得内容还行的点赞支持下吧~