[Shell]-7-处理用户输入
引言
这篇文章主要讲解如何让Bash Shell用不同的方法从用户获取输入并传入脚本进行处理。
文章目录
- 0×1.位置参数变量
- 0×2.位置参数相关特殊变量
- 0×3.使用Shift命令遍历参数
- 0×4.使用getopt命令识别组合参数
- a.getopt与set
- b.实例
- 0×5.使用getopts识别组合参数
- 0×6.用户基本输入
- a.使用echo与read组合
- b.使用read读取数据
- c.输入超时
- d.隐藏输入
- e.自动退出
- f.使用read从文件中读取数据
0×1.位置参数变量
"位置参数"是跟在脚本文件名后面的一串参数列表,用户可以输入N个参数传入脚本,而脚本可以通过以"$"开头的特殊变量名来读取这些传入的参数,请看下面的实例:
#创建一个脚本,这个脚本会依次读取跟随在脚本名后面的12个参数 #这就相当于我们在执行脚本的时候输入下面的命令 # bash b.sh a b c d e f... #对应位置参数就是 # bash b.sh $1 $2 $3 $4 $5 $6.... #$1的值等于a #$2的值等于b,在脚本中调用了$1,就会输出值a,以此类推 #位置参数有几个特殊变量,$0表示脚本文件名 #从9以后的位置需要使用大括号括起来 #这样的特性提供了一个用户到脚本的数据传递接口,我们可以通过在脚本后面携带数据传入脚本,通过$n(n是大于等于0的整数)的形式来调用这些数据 root@qingsword.com:~# vim b.sh #!/bin/bash echo "$0" echo "$1" echo "$2" echo "$3" echo "$4" echo "$5" echo "$6" echo "$7" echo "$8" echo "$9" echo "${10}" echo "${11}" echo "${12}" #尝试携带数据传入脚本,并且用对应的位置参数来输出这些值 root@qingsword.com:~# bash b.sh 1 2 3 4 5 6 7 8 9 10 11 www.qingsword.com b.sh #使用$0输出脚本名称 1 #随后是我们传递给脚本的数据,用空格分割 2 3 4 5 6 7 8 9 10 11 www.qingsword.com
记住,每个参数都是用空格分隔的,所以Bash Shell会将空格当成两个值的分隔符,要在参数值中 包含空格,必须要用引号(单引号或双引号均可)。
root@qingsword.com:~# vim c.sh #!/bin/bash echo "Hello $1" root@qingsword.com:~# bash c.sh qing sword Hello qing root@qingsword.com:~# bash c.sh "qing sword" Hello qing sword
0×2.位置参数相关特殊变量
除了上面那些正常的位置参数外,Bash Shell中还提供了下面这些特殊的变量:
Bash Shell特殊变量:$0, $#, $*, $@, $?, $$
特殊变量列表与含义:
$0 当前脚本的文件名;
$# 传递给脚本或函数的参数个数;
$* 传递给脚本或函数的所有参数;
$@ 传递给脚本或函数的所有参数,被双引号(" ")包含时,与 $* 稍有不同,下面会实例演示;
$? 上个命令的退出状态,或函数的返回值;
$$ 当前Shell进程ID,对于Bash Shell,就是这些脚本所在的进程ID;
$n 传递给脚本或函数的参数,n 是一个大于0的整数,表示第几个参数,例如,第一个参数是$1,第二个参数是$2;
1,$0实例
我们执行脚本时如果是直接使用程序名来执行,那么$0中就显示的是程序名,如果执行过程中携带了完整绝对路径,那么$0就会包含绝度路径,很多时候,我们不想包含绝对路径,想让程序在执行时不管是否使用绝对路径来执行,都只返回文件名,这时就可以使用basename命令返回不包含路径的脚本名。
root@qingsword.com:~# vim d.sh #!/bin/bash echo "$(basename $0)" echo "$0" #执行脚本,不使用绝对路径,两者返回相同的值 root@qingsword.com:~# bash d.sh d.sh d.sh #携带绝对路径,basename命令只返回文件名,而第二条echo中的$0返回了绝对路径 root@qingsword.com:~# bash /root/d.sh d.sh /root/d.sh
2,$#实例
Bash Shell中$#变量存储了脚本携带的参数个数,如果这个值是0表示脚本没有带参数,如果大于0,则表示带有参数。
root@qingsword.com:~# vim a.sh #!/bin/bash echo "Shell脚本后携带了$#个参数" root@qingsword.com:~# bash a.sh hell脚本后携带了0个参数 root@qingsword.com:~# bash a.sh qingsword.com qing sword Shell脚本后携带了3个参数
下面这个实例判断用户有没有携带一个整数参数,如果有,就从1乘到这个数,将结果输出,程序没有做字符串以及浮点数输入判断,如果携带的参数是一个字符串或浮点数,程序会报错。
root@qingsword.com:~# vim aa.sh #!/bin/bash int_a=1 if [ $# -ne 1 ] ; then echo "请在程序调用时携带一个整数,例如:bash aa.sh 3" else echo -n "从$int_a乘到$1的结果为:" for (( int_b=1;int_b<=$1;int_b++ )) ; do int_a=$[ $int_a*$int_b ] done echo "$int_a" fi root@qingsword.com:~# bash aa.sh 请在程序调用时携带一个整数,例如:bash aa.sh 3 root@qingsword.com:~# bash aa.sh 3 2 请在程序调用时携带一个整数,例如:bash aa.sh 3 root@qingsword.com:~# bash aa.sh 3 从1乘到3的结果为:6 root@qingsword.com:~# bash aa.sh 6 从1乘到6的结果为:720
Bash Shell提供了一个特性,能够让我们直接显示最后一个携带的参数值,使用一个特殊的格式${!#},请看下面的实例:
root@qingsword.com:~# vim f.sh #!/bin/bash #显示脚本携带了几个参数 echo "$#" #显示最后一个参数的值 echo "${!#}" #执行脚本,携带4个参数 root@qingsword.com:~# bash f.sh 1 2 3 qingsword.com 4 qingsword.com #最后一个参数值 #如果不带参数,${!#}显示的就是文件名本身,因为最后一个参数就是$0 root@qingsword.com:~# bash f.sh 0 f.sh
3,$*与$@实例
$*和 $@都表示传递给脚本的所有参数集合,不被双引号(" ")包含时,都以"$1" "$2" … "$n" 的形式输出所有参数;
当它们被双引号(" ")包含时,"$*" 会将所有的参数作为一个整体,以"$1 $2 … $n"的形式输出所有参数;"$@" 会将各个参数分开,以"$1" "$2" … "$n" 的形式输出所有参数,请看下面的实例:
root@qingsword.com:~# vim 82.sh #!/bin/bash echo "不使用引号的\$*" for n in $* ; do echo "$n" done echo "不使用引号的\$@" for n in $@ ; do echo "$n" done echo "使用引号的\$*" for n in "$*" ; do echo "$n" done echo "使用引号的\$@" for n in "$@" ; do echo "$n" done #不使用引号的时候,$*和$@输出相同,for会使用IFS分隔符去取$*和$@包含的每个值,而将$*包含在双引号中,会让$*将所有参数当做一个整体输出 root@qingsword.com:~# bash 82.sh aa bb cc dd 不使用引号的$* aa bb cc dd 不使用引号的$@ aa bb cc dd 使用引号的$* aa bb cc dd 使用引号的$@ aa bb cc dd
4,$?实例
$?会返回上个命令的退出状态,如果上一个命令正常执行,$?会返回0,否则会返回非零值,请看下面的实例:
root@qingsword.com:~# vim 7.sh #!/bin/bash echo "www.qingsword.com" echo "上一条命令的返回值$?" #在脚本中加入一条不存在的命令,本站网址 qingsword.com echo "上一条命令的返回值$?" #执行脚本,qingsword.com这条命令不存在,$?就会被写入一个非0值,代表上一条命令执行不成功($?的退出状态码127表示命令不存在,更多退出状态码详见本系列文章第3篇第7小节) root@qingsword.com:~# bash 7.sh www.qingsword.com 上一条命令的返回值0 7.sh: 行 6: qingsword.com: 未找到命令 上一条命令的返回值127
5,$$实例
$$能够显示执行脚本的Bash的PID值,请看下面的实例:
root@qingsword.com:~# vim 55.sh #!/bin/bash echo "当前Bash Shell PID=$$" #执行脚本,得到PID root@qingsword.com:~# bash 55.sh 当前Bash Shell PID=3290
0×3.使用Shift命令遍历参数
Shift命令可以用来移动脚本后面所携带参数的值的位置,在使用Shift命令时,默认情况下它会将每个参数变量向左移动一个位置,所以,变量$3的值会移到$2中,变量$2的值会移到$1中,而变量$1的值则会被删除(注意,变量$0的值,也就是程序名,不会改变),请看下面的实例:
#创建脚本,逐个读取脚本后面携带的变量值,每次循环都使用shift命令,将参数向左移动一位 root@qingsword.com:~# vim h.sh #!/bin/bash count=1 #当$1没有值时结束循环 while [ -n "$1" ] ; do echo "Count $count:=$1" count=$[++count] shift done #执行脚本,这个脚本通过测试第一个参数值的长度执行了一个while循环,当第一个参数的长度为零时,循环结束,测试完第一个参数后,shift命令会将所有参数的位置移动一个位置,使用shift命令的时候要小心,如果$1参数被移出,它的值就被丢弃了,无法再恢复 root@qingsword.com:~# bash h.sh www qing sword com Count 1:=www Count 2:=qing Count 3:=sword Count 4:=com
默认Shift命令的值为1,shell遇到这个命令时将所有脚本携带的参数往前移动一位,可以指定一个移动值,请看下面的实例:
root@qingsword.com:~# vim i.sh #!/bin/bash echo "所有参数:$*" shift 3 #使用shift将参数列表往左移动三次 echo "$1" #本来$1位置是a,使用shift 3后,a、b、c三个参数被往左移掉了,所以$1位置变成了d root@qingsword.com:~# bash i.sh a b c d e f g 所有参数:a b c d e f g d
我们经常看到linux在执行某些命令的时候会携带一些参数,例如ls -l,那么linux是如何读取-l这个参数并处理的呢,请看下面的实例:
root@qingsword.com:~# vim j.sh #!/bin/bash while [ -n "$1" ] ; do case "$1" in -x) echo "读取到-x参数";; -y) echo "读取到-y参数,参数携带值$2" shift;; -z) echo "读取到-z参数";; --) shift #遇到--就退出循环 break;; *) echo "未知参数$1";; esac shift done #读取--后面的参数 int_count=1 for x in "$@" ; do echo "值$int_count:$x" int_count=$[++int_count] done #执行脚本测试 root@qingsword.com:~# bash j.sh -x -y www.qingsword.com -z -- aaa bbb ccc 读取到-x参数 读取到-y参数,参数携带值www.qingsword.com 读取到-z参数 值1:aaa 值2:bbb 值3:ccc #使用未定义的选项-q测试 root@qingsword.com:~# bash j.sh -q -y www.qingsword.com 未知参数-q 读取到-y参数,参数携带值www.qingsword.com
0×4.使用getopt命令识别组合参数
a.getopt与set
1、getopt
在我们使用linux命令行时通常会遇到类似于“ls -al ”这样的命令格式,命令中两个参数-a和-l,可以使用-al的形式写在一起,在我们编写的脚本中,可以通过getopt命令来实现这种格式,请看下面的实例:
#getopt命令格式 #getopts optstring variable # optstring定义了getopts能够包含的选项, variable包含了用户输入的选项表达式 # getopt可以定义多个有效参数选项,例如本例下文的abcde,这些选项中插入冒号的位置表示前面的参数需要携带一个参数值,例如b与c后面就需要携带一个参数值 root@qingsword.com:~# getopt ab:c:de -ab test -c test -de -a -b test -c test -d -e -- root@qingsword.com:~# getopt ab:c:de -ab test -c test -de hehe hehe -a -b test -c test -d -e -- hehe hehe root@qingsword.com:~# getopt abc: -abc test aaa bbb ccc -a -b -c test -- aaa bbb ccc
2、set
在Bash Shell中,"set --"命令能够根据IFS分隔符,依次将其后跟的数据值赋给$1,$2,$3...$N,请看下面的实例:
root@qingsword.com:~# vim test.sh #!/bin/bash set -- a b c echo "$1 $2 $3" #虽然test.sh后面没有携带参数,但脚本中的set将a、b、c分别放入了"$1 $2 $3"三个位置变量中 root@qingsword.com:~# bash test.sh a b c
b.实例
root@qingsword.com:~# vim k.sh #!/bin/bash #set命令将getopt执行的命令结果返回给"$1 $2 $3...$N"位置参数 set -- $(getopt abc:d:ef "$@") while [ -n "$1" ] ; do case "$1" in -a) echo "-a OK";; -b) echo "-b OK";; -c) echo "-c OK parameter $2 OK" shift;; -d) echo "-d OK parameter $2 OK" shift;; -e) echo "-e OK";; -f) echo "-f OK";; --) shift break;; *) echo "$1 Error";; esac shift done count=1 for x in "$@" ; do echo "DATA$count:$x" count=$[++count] done #在脚本后面携带位置参数传入脚本,脚本再通过"set --"命令将getopt处理后的位置参数格式返回给$1 $2 $3...$N对应的位置 root@qingsword.com:~# bash k.sh -abc www.qingsword.com -d qingsword.com -ef data1 data2 data3 -a OK -b OK -c OK parameter www.qingsword.com OK -d OK parameter qingsword.com OK -e OK -f OK DATA1:data1 DATA2:data2 DATA3:data3
0×5.使用getopts识别组合参数
getopts是高级getopt命令,在getopt基础上添加了不少新特性,getopts语法格式如下:
getopts [option[:]] [DESCPRITION] VARIABLE
[option[:]] 表示为某个脚本可以使用的选项,如果选项后面出现了冒号(":"),则表示这个选项后面可以接参数[DESCPRITION];
VARIABLE 表示将某个选项保存在变量VARIABLE中;
getopts对找到的所有未定义的选项统一输出成问号;
getopts是linux系统中的一个内置变量,一般用在循环中,每当执行循环,getopts都会检查下一个命令选项,如果这些选项出现在option中,则表示是合法选项,否则不是合法选项,脚本会将这些合法选项保存在VARIABLE这个变量中;
除此之外,getopts还包含两个内置变量,及OPTARG和OPTIND;
OPTARG 会保存当前迭代的选项后面的参数;
OPTIND 会保存下一个选项或参数的索引;
getopts实例:
root@qingsword.com:~# vim l.sh #!/bin/bash #[option[:]]中最前面的冒号用于去掉错误消息,简而言之,想去掉错误消息的话,可以在选项参数之前加一个冒号 #每次循环从用户输入读取的单个选项值都会被保存到opt变量 while getopts :ab:d:ef opt ; do case "$opt" in a) echo "-a OK";; #当前选项是-b,OPTARG变量就会保存-b后面的值,以此类推 b) echo "-b OK value $OPTARG";; d) echo "-d OK value $OPTARG";; e) echo "-e OK";; f) echo "-f OK";; #如果读取到一个未定义的选项,getopts默认会输出一个?号 *) echo "Unknow Option:$opt" esac done #OPTIND保存了参数列表下一个选项或参数的索引,所以当getopts读取完所有的选项后,如果需要处理接下来的数据部分,就需要使用shift将前面所有处理完的选项全部向左移动清除掉 shift $[--OPTIND] count=1 for x in "$@" ; do echo "Parameter $count=$x" count=$[++count] done root@qingsword.com:~# bash l.sh -ab qingsword -cd www.qingsword.com -ef aaa bbb ccc -a OK -b OK value qingsword Unknow Option:? -d OK value www.qingsword.com -e OK -f OK Parameter 1=aaa Parameter 2=bbb Parameter 3=ccc
在Linux中,尽量使用下面这样的标准输入选项参数,方便让其他人理解参数作用:
-a 显示所有对象;
-c 生成一个计数;
-d 指定一个目录;
-e 扩展一个对象;
-f 指定读入数据的文件;
-h 显示命令的帮助信息;
-i 忽略文本大小写;
-l 产生输出的长格式版本;
-n 使用非交互模式(批处理);
-o 将所有输出重定向到的指定的输出文件;
-q 以安静模式运行;
-r 递归地处理目录和文件;
-s 以安静模式运行;
-v 生成详细输出;
-x 排除某个对象;
-y 对所有问题回答yes;
0×6.用户基本输入
a.使用echo与read组合
read命令从标准输入中读取一行,用IFS(内部字段分隔符)变量中的字符作为分隔符分割出每个字段,并把输入行的每个字段的值赋予给指定的变量,请看下面的实例:
root@qingsword.com:~# vim m.sh #!/bin/bash echo -n "Enter your Website:" #将用户输入的值赋予给website变量 read website echo "Website:$website" #执行 root@qingsword.com:~# bash m.sh Enter your Website:www.qingsword.com Website:www.qingsword.com
b.使用read读取数据
read命令可以使用-p参数直接输出提示数据,不需要使用echo,请看下面的实例:
root@qingsword.com:~# vim o.sh #!/bin/bash #"Name:"会直接显示在屏幕上等待用户输入,并将用户的输入保存到str_a变量中 read -p "Name:" str_a echo "Hello $str_a" #执行 root@qingsword.com:~# bash o.sh Name:qing sword Hello qing sword
如果在read后面跟多个变量,那么read会分割用户输入内容分别存入后面的变量中,如果变量不够,那么剩下的数据就会都存入最后那个变量中,请看下面的实例:
root@qingsword.com:~# vim o.sh root@qingsword.com:~# more o.sh #!/bin/bash read -p "Name:" str_a str_b echo "Hello $str_a" echo "Hello $str_b" #执行测试 root@qingsword.com:~# bash o.sh Name:qing sword Hello qing Hello sword #当输入的字段大于接收的变量数量,最后那个变量将接收完所有的用户输入 root@qingsword.com:~# bash o.sh Name:www qing sword com Hello www Hello qing sword com
另一个实例,输入年,计算天数(按标准一年365天计算):
root@qingsword.com:~# vim n.sh #!/bin/bash read -p "Enter your age:" age #将age变量乘以365,发送给bc计算机处理 days=$(echo "scale=3;$age*365" |bc) echo "$age years $days days" #执行 root@qingsword.com:~# bash n.sh Enter your age:18 18 years 6570 days
如果在read命令行中不指定接收值的变量,read命令会将它收到的任何数据都放进特殊环境变量REPLY,这是Bash Shell预设的环境变量,请看下面的实例:
root@qingsword.com:~# vim p.sh #!/bin/bash #在read后没有指定任何变量名称 read -p "Enter your name:" #用户输入的数据被保存到REPLY变量中 echo "Hello $REPLY" #执行 root@qingsword.com:~# bash p.sh Enter your name:www.qingsword.com qingsword Hello www.qingsword.com qingsword
c.输入超时
可以使用read命令的-t参数指定一个用户输入时间(单位秒),如果超过这个时间用户未输入,那么脚本将自动退出read命令的读取,继续执行接下来的指令,请看下面的实例:
root@qingsword.com:~# vim q.sh #!/bin/bash #当计时器过期后,read命令会返回一个非零退出状态码 if read -t 6 -p "Enter your name:" ; then echo "Hello $REPLY" else echo "Too slow" fi #执行测试,故意不输入内容,经过6秒,程序返回"Too slow" root@qingsword.com:~# bash q.sh Enter your name:Too slow #第二次测试,输入数据 root@qingsword.com:~# bash q.sh Enter your name:www.qingsword.com Hello www.qingsword.com
d.隐藏输入
read命令中的-s参数可用于隐藏输入,-s选项可以避免在read命令中输入的数据出现在屏幕上(实际上,数据会被显示,只是read命令会将文本颜色设成跟背景色一样),请看下面的实例:
root@qingsword.com:~# vim s.sh #!/bin/bash read -s -p "Enter Your Password:" echo "" #用于换行 echo "Password $REPLY Changed" #执行 root@qingsword.com:~# bash s.sh Enter Your Password: Password www.qingsword.com Changed
e.自动退出
#本例将-n选项的值设置成1,告诉read命令在接受单个字符输入后退出,只要按下单个字符,read 命令就会将输入的单个字符传给变量,无需按回车键 root@qingsword.com:~# vim r.sh #!/bin/bash read -n 1 -p "Countinue[Y/N]?" answer case $answer in Y | y) echo "" echo "continue...";; N | n) echo "" echo "Bye...";; esac echo "End..." #执行脚本 root@qingsword.com:~# bash r.sh Countinue[Y/N]?n Bye... End... root@qingsword.com:~# bash r.sh Countinue[Y/N]?Y continue... End...
f.使用read从文件中读取数据
read除了能够从键盘读取输入外,还能用来逐行读取文件内容,请看下面的实例:
root@qingsword.com:~# vim v.sh #!/bin/bash count=1 echo "Start..." #使用管道命令将cat输出传入while循环,循环使用read逐行读取cat文件流 cat "test.txt" | while read ; do #另一种写法,使用line变量接收每行数据,本例直接使用默认的REPLY变量 #cat "test.txt" | while read line ; do echo "Line $count:$REPLY" #echo "Line $count:$line" count=$[++count] done echo "End..." #test.txt文件内容如下 root@qingsword.com:~# more test.txt www.qingsword.com qingsword.com qingsword #执行脚本测试 root@qingsword.com:~# bash v.sh Start... Line 1:www.qingsword.com Line 2:qingsword.com Line 3:qingsword End…