Linux命令行与Shell脚本编程
第十三章 处理用户输入
三.处理用户输入
1,传递参数
向 shell脚本传递数据的最基本方法是使用命令行参数。
命令行参数允许运行脚本时在命令行中添加数据:
$ ./addem 10 30
如何在bash shell脚本中使用命令行参数?
1.1,读取参数 $1~9 ${10~n}
bash shell会将所有的命令行参数都指派给称作 位置参数。这也包括shell脚本名称。
位置变量 的名称都是标准数字:$0对应脚本名,$1对应第一个命令行参数,$2对应第二个命令行参数,以此类推,直到$9.参数之间必须用空格分开.
$ cat positional1.sh
#!/bin/bash
echo $1
echo $2
$ ./positional1.sh 1 ok
5
ok
带空格的参数需要使用 "" 或 ''.
如果脚本需要的命令行参数不止9个,仍可以继续加入更多的参数,但在之后,必须在变量名两侧加上花括号,比如${10}。
1.2,读取脚本名 basename $0
使用位置变量$0获取在命令行中运行的shell脚本名。
拥有一个能 识别自己 的脚本对于追踪脚本问题、系统审计和生成日志信息是非常有用的。
$ cat positional0.sh
#!/bin/bash
echo This script name is $0.
exit
$ bash positional0.sh
This script name is positional0.sh.
如果使用另一个命令来运行shell脚本,则命令名会和脚本名混在一起,出现在位置变量$0中:
$ ./positional0.sh
This script name is ./positional0.sh.
如果运行脚本时使用的是绝对路径,那么位置变量$0就会包含整个路径:
$ $HOME/scripts/positional0.sh
This script name is /home/christine/scripts/positional0.sh.
basename命令可以返回不包含路径的脚本名:
$ cat posbasename.sh
#!/bin/bash
name=$(basename $0)
echo This script name is $name.
exit
$ ./posbasename.sh
This script name is posbasename.sh.
1.3,参数测试 if [ -n “$1” ]
在shell脚本中使用命令行参数时要当心。如果运行脚本时没有指定所需的参数,则可能会出问题.
当脚本认为位置变量中应该有数据,而实际上根本没有的时候,脚本很可能会产生错误消息。
在使用位置变量之前一定要检查是否为空:
$ cat checkpositional1.sh
#!/bin/bash
if [ -n "$1" ]
then
factorial=1
for (( number = 1; number <= $1; number++ ))
do
factorial=$[ $factorial * $number ]
done
echo The factorial of $1 is $factorial
else
echo "You did not provide a parameter."
fi
exit
$ ./checkpositional1.sh
You did not provide a parameter.
$ ./checkpositional1.sh 3
The factorial of 3 is 6
2,特殊参数变量
跟踪命令行参数的 特殊变量。
2.1,参数统计 $# ${!#}
在脚本中使用命令行参数之前应该检查一下 位置变量。对使用多个命令行参数的脚本来说很麻烦
统计一下有多少个命令行参数。bash shell为此提供了一个特殊变量。
特殊变量$#含有脚本运行时携带的命令行参数的个数。跟普通变量一样可以在脚本中的任何地方使用这个特殊变量.
$ cat addem.sh
#!/bin/bash
if [ $# -ne 2 ]
then
echo Usage: $(basename $0) parameter1 parameter2
else
total=$[ $1 + $2 ]
echo $1 + $2 is $total
fi
exit
$
$ ./addem.sh
Usage: addem.sh parameter1 parameter2
$ ./addem.sh 17
Usage: addem.sh parameter1 parameter2
$ ./addem.sh 17 25
17 + 25 is 42
使用 !#获取变量,不能使用$#,当命令行中没有任何参数时,$#的值即为0,但${!#}会返回命令行中的脚本名。
$ cat goodlastparamtest.sh
#!/bin/bash
echo The number of parameters is $#
echo The last parameter is ${!#}
exit
$ ./goodlastparamtest.sh one two three four
The number of parameters is 4
The last parameter is four
$ ./goodlastparamtest.sh
The number of parameters is 0
The last parameter is ./goodlastparamtest.sh
2.2,获取所有的数据 $* $@
抓取命令行中的所有参数。用两个特殊变量即可.
$* 变量和 $@变量 可以轻松访问所有参数,它们各自包含了所有的命令行参数。
$*变量 将所有的命令行参数 视为一个单词。
$@变量 将所有的命令行参数 视为同一字符串中的多个独立的单词,以便你能遍历并处理全部参数
$ cat grabbingallparams.sh
#!/bin/bash
echo "Using the \$* method: $*"
echo "Using the \$@ method: $@"
exit
$ ./grabbingallparams.sh alpha beta charlie delta
Using the $* method: alpha beta charlie delta
Using the $@ method: alpha beta charlie delta
$ cat grabdisplayallparams.sh
#!/bin/bash
echo "Using the \$* method: $*"
count=1
for param in "$*"
do
echo "\$* Parameter #$count = $param"
count=$[ $count + 1 ]
done
echo "Using the \$@ method: $@"
count=1
for param in "$@"
do
echo "\$@ Parameter #$count = $param"
count=$[ $count + 1 ]
done
exit
$ ./grabdisplayallparams.sh alpha beta charlie delta
Using the $* method: alpha beta charlie delta
$* Parameter #1 = alpha beta charlie delta
Using the $@ method: alpha beta charlie delta
$@ Parameter #1 = alpha
$@ Parameter #2 = beta
$@ Parameter #3 = charlie
$@ Parameter #4 = delta
3,移动参数 shift n
shift命令 可用于操作命令行参数。shift命令会根据 命令行参数 的相对位置进行移动。
使用shift命令时,默认情况下会将每个 位置变量 值都向左移动一个位置.
因此变量$3的值会移入$2,变量$2的值会移入$1,而变量$1的值则会被删除(变量$0的值(脚本名)不会改变).
数被移出,值就被丢弃无法再恢复。!!
在不知道到底有多少参数的时候。你可以只操作第一个位置变量,移动参数,然后继续处理该变量;
$ cat shiftparams.sh
#!/bin/bash
echo
echo "Using the shift method:"
count=1
while [ -n "$1" ]
do
echo "Parameter #$count = $1"
count=$[ $count + 1 ]
shift
done
echo
exit
$ ./shiftparams.sh alpha bravo charlie delta
Using the shift method:
Parameter #1 = alpha
Parameter #2 = bravo
Parameter #3 = charlie
Parameter #4 = delta
可以一次性移动多个位置。提供参数,指明要移动的位置数.
4,处理选项
选项是在连字符之后出现的单个字母,能够改变命令的行为。
4.1,查找选项
可以像处理命令行参数一样处理命令行选项。
4.1.1,处理简单选项
可以使用 shift 处理命令行选项.使用 case判断 是否为参数;
$ cat extractoptions.sh
#!/bin/bash
echo
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
echo
exit
$ ./extractoptions.sh -a -b -c -d
Found the -a option
Found the -b option
Found the -c option
-d is not an option
4.1.2,分离参数和选项 –
同时使用选项和参数的情况时,使用特殊字符双连字符(--)将两者分开,字符告诉脚本选项何时结束,普通参数何时开始.
shell 用双连字符表明选项部分结束。在双连字符后,脚本将剩下的部分作为参数处理.
$ cat extractoptionsparams.sh
#!/bin/bash
echo
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
echo
exit
$ ./extractoptionsparams.sh -a -b -c -- test1 test2 test3
Found the -a option
Found the -b option
Found the -c option
Parameter #1: test1
Parameter #2: test2
Parameter #3: test3
4.1.3,处理含值的选项 shift
有些选项需要一个额外的参数值。类似:
$ ./testing.sh -a test1 -b -c -d test2
当命令行选项要求额外的参数时,脚本必须能够检测到并正确地加以处理。
$ cat extractoptionsvalues.sh
#!/bin/bash
echo
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
exit
$ ./extractoptionsvalues.sh -a -b BValue -d
Found the -a option
Found the -b option with parameter value BValue
-d is not an option
4.2,getopt命令
合并选项是一种很常见的用法,希望脚本有良好的用户体验,特性不能少.
$ ./extractoptionsvalues.sh -ac
-ac is not an option
getopt 命令能够识别命令行参数,简化解析过程。
4.2.1,命令格式 :
getopt命令可以接受一系列任意形式的命令行选项和参数.
格式:
getopt optstring parameters
optstring 定义了有效的命令行选项字母 和 哪些选项字母需要参数值。
在 optstring 中列出要在脚本中用到的每个命令行选项字母。
在每个需要参数值的选项字母后面加一个冒号。 !!!
getopt命令会基于定义的 optstring 解析提供的参数。
$ getopt ab:cd -a -b BValue -cd test1 test2
-a -b BValue -c -d -- test1 test2
optstring 未包含指定的选项,默认情况下,getopt命令会产生一条错误消息, 使用 -q 选项忽略.
$ getopt ab:cd -a -b BValue -cde test1 test2
getopt: invalid option -- 'e'
-a -b BValue -c -d -- test1 test2
$ getopt -q ab:cd -a -b BValue -cde test1 test2
-a -b 'BValue' -c -d -- 'test1' 'test2'
4.2.2,在脚本中使用getopt + set
要使用 getopt命令生成的 格式化版本替换已有的命令行选项和参数。需要使用 set命令。
set 命令有一个选项是双连字符(--),可以将 位置变量的值 替换成 set命令所指定的值。
将脚本的命令行参数传给 getopt命令,再将 getopt命令的输出传给set命令,用getopt格式化后的命令行参数来替换原始的命令行参数.
位置变量原先的值会被 getopt命令的输出替换掉。
set -- $(getopt -q ab:cd "$@")
示例:
$ cat extractwithgetopt.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
exit
$ ./extractwithgetopt.sh -ac
Found the -a option
Found the -c option
## getopt命令 不擅长处理带空格和引号的参数值。会将空格当作参数分隔符!!
$ ./extractwithgetopt.sh -c -d -b BValue -a "test1 test2" test3
Found the -c option
-d is not an option
Found the -b option with parameter value 'BValue'
Found the -a option
Parameter #1: 'test1
Parameter #2: test2'
Parameter #3: 'test3'
4.2.3,使用getopts命令
getopts相比 getopt 多了一些扩展功能。
getopt在将命令行中选项和参数处理后只生成一个输出,getopts 能够和已有的 shell位置变量配合默契。
getopts每次只处理一个检测到的命令行参数。在处理完所有的参数后,getopts会退出并返回一个大于0的退出状态码。非常适合用在解析命令行参数的循环中。
getopts命令有 可以在参数值中加入空格.
格式:
getopts optstring variable
有效的选项字母 在 optstring 中列出,
如果选项字母要求有参数值,就在其后加一个冒号。
不想显示错误消息的话,可以在 optstring 前加一个冒号 :。
getopts命令 将当前参数保存在命令行中定义的 variable 中。
getopts 命令要用到两个环境变量。
OPTARG 环境变量保存 选项需要加带的参数值。
OPTIND 环境变量保存 getopts正在处理的参数位置。在处理完当前选项之后就能继续处理.
while语句定义了getopts命令,指定要查找哪些命令行选项,以及每次迭代时存储它们的变量名(opt)。
示例:
$ cat extractwithgetopts.sh
#!/bin/bash
while getopts :ab:c opt
do
case "$opt" in
a) echo "Found the -a option" ;; ##getopts命令会移除起始的连字符,所以在case语句中不用连字符。
b) echo "Found the -b option with parameter value $OPTARG";;
c) echo "Found the -c option" ;;
*) echo "Unknown option: $opt" ;;
esac
done
exit
$ ./extractwithgetopts.sh -ab "BValue BValue2" -c
Found the -a option
Found the -b option with parameter value BValue BValue2
Found the -c option
可以将选项字母和参数值写在一起 之间不加空格:
$ ./extractwithgetopts.sh -abBValue
Found the -a option
Found the -b option with parameter value BValue
可以将在命令行中找到的所有未定义的选项统一输出成问号,optstring中未定义的选项字母会以问号形式传给脚本:
$ ./extractwithgetopts.sh -ade
Found the -a option
Unknown option: ?
Unknown option: ?
getopts 命令知道何时停止处理选项,并将参数留给你处理。
在处理每个选项时,getopts会将OPTIND环境变量值增1。处理完选项后,可以使用 shift命令 和 OPTIND值 来移动参数:
$ cat extractoptsparamswithgetopts.sh
#!/bin/bash
while getopts :ab:cd opt
do
case "$opt" in
a) echo "Found the -a option" ;;
b) echo "Found the -b option with parameter value $OPTARG";;
c) echo "Found the -c option" ;;
d) echo "Found the -d option" ;;
*) echo "Unknown option: $opt" ;;
esac
done
echo $OPTIND
echo $@
shift $[ $OPTIND - 1 ]
echo $@
echo $OPTIND
count=1
for param in "$@"
do
echo "Parameter $count: $param"
count=$[ $count + 1 ]
done
exit
$ ./extractoptsparamswithgetopts.sh -db BValue test1 test2
Found the -d option
Found the -b option with parameter value Bvalue
3
-db Bvalue test1 test2
test1 test2
3
Parameter 1: test1
Parameter 2: test2
5,选项标准化
在Linux中,有些选项字母在某种程度上已经有了标准含义。在shell脚本中支持这些选项,脚本会更友好。
选项 | 描述 |
---|---|
-a | 显示所有对象 |
-c | 生成计数 |
-d | 指定目录 |
-e | 扩展对象 |
-f | 指定读入数据的文件 |
-h | 显示命令的帮助信息 |
-i | 忽略文本大小写 |
-l | 产生长格式输出 |
-n | 使用非交互模式(批处理) |
-o | 将所有输出重定向至指定的文件 |
-q | 以静默模式运行 |
-r | 递归处理目录和文件 |
-s | 以静默模式运行 |
-v | 生成详细输出 |
-x | 排除某个对象 |
-y | 对所有问题回答yes |
6,获取用户输入 read
尽管命令行选项和参数是从脚本用户处获取输入的一种重要方式,但有时候可能想要在脚本运行时询问用户并等待用户回答。
bash shell提供了read命令。
6.1,基本读取 read -p $REPLY
read命令从标准输入(键盘)或另一个文件描述符中接受输入。获取输入后,read命令 将数据存入变量。
$ cat askname.sh
#!/bin/bash
echo -n "Enter your name: " ## echo -n选项,不会在字符串末尾输出换行符.
read name
echo "Hello $name, welcome to my script."
exit
$ ./askname.sh
Enter your name: Richard Blum
Hello Richard Blum, welcome to my script.
read命令也提供了-p选项,允许直接指定存入的变量提示符:
$ cat askage.sh
#!/bin/bash
read -p "Please enter your age: " age
days=$[ $age * 365 ]
echo "That means you are over $days days old!"
exit
$ ./askage.sh
Please enter your age: 30
That means you are over 10950 days old!
如果指定多个变量,则输入的每个数据值 都会分配给 变量列表中的下一个变量。输入数据多于变量数量,那么剩余的数据就全部分配给最后一个变量.
$ cat askfirstlastname.sh
#!/bin/bash
read -p "Enter your first and last name: " first last
echo "Checking data for $last, $first..."
exit
$ ./askfirstlastname.sh
Enter your first and last name: Richard Blum
Checking data for Blum, Richard...
可以 在read命令中不指定任何变量,此时 read命令便会将接收到的所有数据都放进 特殊环境变量 REPLY 中(也仅在不指定变量时才放入 REPLY):
REPLY环境变量包含输入的所有数据
$ cat asknamereply.sh
#!/bin/bash
read -p "Enter your name: "
echo
echo "Hello $REPLY, welcome to my script."
exit
$ ./asknamereply.sh
Enter your name: Christine Bresnahan
Hello Christine Bresnahan, welcome to my script.
6.2,超时 read -t
使用 read命令时,脚本可能会一直苦等着用户输入。
如果不管是否有数据输入,脚本都必须继续执行,可以用-t选项来指定计时器。
-t选项会指定 read命令 等待输入的 秒数。如果计时器超时,则read命令会返回非0退出状态码.
$ cat asknametimed.sh
#!/bin/bash
if read -t 5 -p "Enter your name: " name
then
echo "Hello $name, welcome to my script."
else
echo "Sorry, no longer waiting for name."
fi
exit
$ ./asknametimed.sh
Enter your name: Christine
Hello Christine, welcome to my script.
$ ./asknametimed.sh
Enter your name:
Sorry, no longer waiting for name.
可以不对输入过程计时,而是统计输入的字符数。当字符数达到预设值时,就自动退出,将已输入的数据赋给变量:
-n选项和数值,告诉read命令在接收到x个字符后退出。
$ cat continueornot.sh
#!/bin/bash
read -n 1 -p "Do you want to continue [Y/N]? " answer
case $answer in
Y | y) echo
echo "Okay. Continue on...";;
N | n) echo
echo "Okay. Goodbye"
exit;;
esac
echo "This is the end of the script."
exit
$ ./continueornot.sh
Do you want to continue [Y/N]? Y
Okay. Continue on...
This is the end of the script.
$ ./continueornot.sh
Do you want to continue [Y/N]? n
Okay. Goodbye
6.3,无显示读取 read -s
需要从脚本用户处得到输入,而不在屏幕上显示输入信息
s选项 可以避免在 read命令中输入的数据出现在屏幕上(其实数据还是会被显示,只不过read命令将文本颜色设成了跟背景色一样)
$ cat askpassword.sh
#!/bin/bash
read -s -p "Enter your password: " pass
echo
echo "Your password is $pass"
exit
$ ./askpassword.sh
Enter your password:
Your password is Day31Bright-Test
6.4,从文件中读取 cat File | while read var
可以使用read命令读取文件。每次调用read命令都会从指定文件中读取一行文本。
当文件中没有内容可读时,read命令会退出并返回 非0退出状态码。
将文件数据传给 read命令。最常见的方法是对文件使用cat命令,将结果通过管道直接传给含有read命令的while命令
$ cat readfile.sh
#!/bin/bash
count=1
cat $HOME/scripts/test.txt | while read line
do
echo "Line $count: $line"
count=$[ $count + 1 ]
done
echo "Finished processing the file."
exit
$ cat $HOME/scripts/test.txt
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?
$ ./readfile.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.