《Linux命令行与shell脚本编程大全》第十四章 处理用户输入
有时还会需要脚本能够与使用者交互。bash shell提供了一些不同的方法来从用户处获得数据,
包括命令行参数,命令行选项,以及直接从键盘读取输入的能力。
14.1 命令行参数
就是添加在命令后的数据
比如: ./addem 10 30
10 和 30 就是传递的命令行参数
14.1.1 读取参数
bash shell会将一些称为位置参数的特殊变量分配给输入到命令行中的所有参数。
比如: $0:程序名 $1:第一个参数 $2:第2个参数 …… 以此类推直到第九个参数。
每个参数都必须用空格分开。可以使用数字也可以使用文本。如果文本有空格,需要用引号括起来,(单引号双引号均可)
若参数不止9个,第十个以后的需要加花括号处理。比如${10}:第十个参数 ${11}:第11个参数。这样就可以添加任意多的参数了。
14.1.2 读取脚本名
$0 参数获取shell在命令行启动的脚本名。
也就是说 同一个脚本test。 ./test 运行 和 /xcy/home/test 这样会得到不同的结果。
basename会返回不包含路径的脚本名。比如 name=$(basename $0)
例子:
1 #!/bin/bash
2 # canshu test
3 echo "Command Name is $0"
4 name=$(basename $0)
5 echo "file name is $name"
6 echo "Hi, My name is $1, I am $2, I am come from $3"
7 echo "Param4: $4, Param5:$5 ...... Param10:${10}, Param11:${11}"
运行:
14.1.3 测试参数
如果脚本中使用了命令行参数,脚本不加参数运行可能会出问题。
可以先判断参数是否存在。使用前此案检查是否存在数据
使用 –n 来检查命令行参数是否有数据。
例子:
1 #/bin/bash
2 # check param
3 if [ -n "$1" ] # 需要用双引号括起来
4 then
5 echo "hello, $1, nice to meet you"
6 else
7 echo "sorry, you are illegality"
8 fi
运行:可以这样
./test2
./test2 xiaochongyong
14.2 特殊参数变量
14.2.1 参数统计
$# 可以统计参数个数,统计输入了多少个参数
假如有5个参数,那么 $# 会是5。 那么${$#}就应该是最后一个参数。然而其实并不是这样。
其实不能在花括号里面是有美元符。必须换成感叹号。${!#}
14.2.2 抓取所有的变量
抓取命令行上提供的所有参数。
$* 和 $@ 变量可以用来访问所有的参数。
$*:将命令行上提供的所有参数当做一个单词保存,可以看做一个整体
$@:将命令行上提供的所有参数当做同一字符串中的多个独立的单词。可以用for遍历所有的参数值。
例子: 这里的反斜杠是为了转义美元符
1 #!/bin/bash
2 # test
3 echo "Param count:$#"
4 echo "Last Param:${!#}"
5 echo "param \$*:$*"
6 echo "param \$@:$@"
7 count=1
8 for param in "$*" # for param in $*, no ""。如果没有双引号,效果一样
9 do
10 echo " Param \$* #$count = $param"
11 count=$[ $count + 1 ]
12 done
13
14 count=1
15 for param1 in "$@"
16 do
17 echo " Param \$@ #$count = $param1"
18 count=$[ $count + 1 ]
19 done
运行:./test 1 2 3 4 5
注意第10行和第17行的输出的区别
14.3 移动变量
bash shell的shift工具命令能操作命令行参数。
shift命令会根据它们的相对位置来移动命令行参数。
如何使用:默认情况下将每个参数变量向左移动一个位置。
$3移动到$2, $2移动到$1。$1的值就被丢弃了,$0的值不会被改变。
1 #!/bin/bash
2 # shift test
3 count=1
4 while [ -n "$1" ]
5 do
6 echo "Param$count = $1"
7 count=$[ $count + 1 ] # 这个是数学运算
8 shift
9 done
运行: ./test 1 2 3 4 5 6 7
还可以一次性移动多个, shift n n表示移动位置数
14.4 处理选项
选项:跟在单破折线后面的单个字母,它能改变命令的行为。比如 ls -a
14.4.1 查找选项
1.处理简单选项。
比如下面的例子:
1 #!/bin/bash
2
3 if [ -n "$1" ]
4 then
5 case "$1" in
6 -a) echo "this is -a option";;
7 -b) echo "this is -b option";;
8 -c) echo "this is -c option";;
9 -d) echo "this is -d option";;
10 esac
11 else
12 echo "no param"
13 fi
运行:
./test -a
./test –b
2. 分离参数和选项
如果需要在shell脚本中同时使用选项和参数。
解决方案是用特殊字符来将二者分开。
shell会用双破折线来表明选项列表结束。在双破破折现之后脚本就可以放心的将剩下的命令当做参数了。而不是选项
运行脚本时, -- 之前的是选项和选项的参数。 -- 的之后就是命令行参数。
3. 处理带值的参数
有时候选项会带上一个额外的参数值。
由于要处理的选项是$1 ,那么参数就在它后面,就是$2.
例子:
1 #!/bin/bash
2 echo
3 while [ -n "$1" ]
4 do
5 case "$1" in
6 -a) echo "Found the -a option";;
7 -b) value=$2 # 这里处理选项参数
8 echo "Found the -b option, Param = $value"
9 shift;;
10 -c) echo "Found the -c option";;
11 --) shift
12 break;;
13 *) echo "$1 is not option";;
14 esac
15 shift
16 done
17 # 上面处理选项,下面是处理命令行参数
18 count=1
19 for param in $@
20 do
21 echo "Param$count = $param"
22 count=$[ $count + 1 ]
23 done
运行:
14.4.2 使用getopt命令
它能够识别命令行参数,从而在脚本中解析它们。
1.命令的格式
可以接受一系列任意形式的命令行选项和参数,并自动将它们转换成命令行参数。
命令格式如下:
getopt optstring parameters
optstring:定义了命令行有效的选项字母,还定义了哪些选项字母需要参数值
比如:
$getopt ab:cd –a –b testb –cd test2 test3
b后面接了冒号:说明-b选项需要参数。
后面 –cd 会被自动拆分成 –c -d 两个单独的选项
还会插入双破折线来分割行中的额外参数。
2.在脚本中使用getopt
可以在脚本中使用getopt来格式化脚本所携带的任何命令行选项或者参数,用起来略繁琐。
方法是:用getopt命令生成的格式化后的版本来替换已有的命令行参数和选项。
用set命令可以做到。需要的选项之一就是 双破折号 --。它会将命令行参数替换成set命令的命令行值。
但是这个不擅长处理带空格和带引号的参数值,它会将空格当做参数分隔符,而不是根据双引号将二者当做一个参数
例子:
1 #!/bin/bash
2 echo
3 set -- $(getopt -q ab:cd: "$@")
4 while [ -n "$1" ]
5 do
6 case "$1" in
7 -a) echo "Found the -a option";;
8 -b) value=$2
9 echo "Found the -b option, Param = $value"
10 shift;;
11 -c) echo "Found the -c option";;
12 -d) value=$2
13 echo "Found the -d option, Param = $value"
14 shift;;
15 --) shift
16 break;;
17 *) echo "$1 is not option";;
18 esac
19 shift
20 done
21
22 count=1
23 for param in $@
24 do
25 echo "Param$count = $param"
26 count=$[ $count + 1 ]
27 done
运行:
3. 使用更高级的getopts
getopt将命令行上选项和参数的处理结后只生成一个输出。
getopts能够和已有的shell参数变量配合默契。
每次调用它时,它一次只处理命令行上检测到的一个参数。处理完所有的参数后,会退出并返回一个大于0的退出状态码。这样就可以循环了。
格式如下:
getopts optstring variable
如果选项有参数就在后面加冒号
要去掉错误消息就在前面加冒号。
getopts会用到两个环境变量:
OPTARG:会保存一个参数值
OPTIND:保存参数列表中getopts正在处理的参数位置。
getopts知道何时停止处理选项,并且把参数留给你处理。
每处理一个选项,OPTIND就会增1,处理完选项时就可以用shift来移动参数。从而处理参数。
例子:
1 #!/bin/bash
2 echo
3 while getopts :ab:c opt
4 do
5 case "$opt" in
6 a) echo "Found -a option";;
7 b) echo "Found -b option, with value:$OPTARG";;
8 c) echo "Found -c option";;
9 *) echo "Unkonw option: $opt"
10 esac
11 done
12
13 echo
14 echo "OPNTING = $OPTIND"
15 shift $[ $OPTIND - 1 ] #这里开始处理参数,先移动参数
16 count=1
17 for param in "$@"
18 do
19 echo "Param$count = $param"
20 count=$[ $count + 1 ]
21 done
运行:
14.5 将选项标准化
有一些选项是标准化了,约定俗成的东西。自己写的脚本中可以支持这些选项。这样用户使用你的脚本就不用查手册了。
比如:
-a 显示所有对象
-c 生成一个计数
-h 显示命令的帮助信息
-I 忽略文本大小写
-v 生成详细输出
……
14.6 获得用户输入
使用场景:脚本运行时可以问个问题,并等待运行脚本的人来回答。
为此提供了read命令
14.6.1 基本的读取
read命令从标准输入或另外一个文件描述符中接收输入。收到输入后read将数据放进一个变量中去。
read 还可以接 –p选项,允许在read命令行指定提示符。
还可以指定多个变量,如果变量数量不够,那么剩下的数据就全部分配到最后一个变量中。
如果没有指定变量名,会放到环境变量REPLY中。
例子:
1 #!/bin/bash
2 echo -n "Please input you name:"
3 read name
4 echo "Hello $name, welcome to xcy program"
5 read -p "Please input you age: " age # 指定提示符
6 echo "you are live:$[ $age * 365 ] days"
7 read -p "step2 Please input you company and num:" company num # 多个变量
8 echo "you company:$company, num:$num"
9 read -p "REPLY test:" # 不值得变量名
10 echo "You input REPLY = $REPLY"
运行:
14.6.2 超时
如果使用了read,脚本会一直等待用户输入。
如果不管脚本是否有数据输入都继续执行下去,就可以指定一个定时器。
-t 指定一个计时器,指定了read命令等待输入的秒数。定时器过期后,read返回一个非0退出状态码。
还可以不对输入过程计时,而是统计字数,字数到了就自动退出,将输入数据赋给变量。
用-n选项,后面接字数
例子:
1 #/bin/bash
2 if read -t 10 -p "Input you name:" name # 计时
3 then
4 echo "Hi $name, welcome"
5 else
6 echo
7 echo "sorry, to slow"
8 fi
9
10 read -n1 -p "Do you want to continue [Y/N]? " answer # 限定字符
11 case $answer in
12 Y) echo
13 echo "you are continue ......";;
14 N) echo
15 echo "Bye Bye!!!";;
16 *) echo
17 echo "Error, Bye";;
18 esac
14.6.3 隐藏方式读取
比如输入密码,不想别人看见,就需要隐藏输入。
加 –s 选项
比如:
1 #!/bin/bash
2 read -s -p "input you Pass:" Pass
3 echo
4 read -s -p "input you Pass again:" Pass1
5 echo
6 if [ $Pass = $Pass1 ] # 字符串比较是否相同
7 then
8 echo "Success!!!"
9 else
10 echo "Falied, Two inconsistent"
11 fi
14.6.4 从文件中读取
每次调用read命令,它都会从文件中读取一行文本。当文本没有内容时,read返回非零退出状态码。
难点在于将文件中的数据传给read命令。最常见的方法是对文件使用cat命令,将结果通过管道直接传给含有read命令的while命令。
例子:
1 #!/bin/bash
2 read -p "Please input you read file name:" name
3 if [ -e $name ] # 是否存在
4 then
5 count=1
6 cat $name | while read line
7 do
8 echo "Line $count:$line"
9 count=$[ $count + 1 ]
10 done
11 else
12 echo "$name not exist"
13 fi