Shell编程—处理用户输入

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命令以非零退出状态码退出。

上一篇:手写spring+springmvc+mybatis框架篇【springmvc】


下一篇:Jenkins(7)参数化构建(构建git仓库分支)