Handling-User-Input
本章内容:
- Passing parameters
- Tracking parameters
- Being shifty
- Working with options
- Standardizing options
- Getting user input
# Passing Parameters
# Command Line Parameters
Bash 会给运行脚本的每个命令行参数赋值给不同的变量,比如:
$0: script's name$1: first parameter
...$9: ninth parameter${10}: tenth parameter
...
举例:
$ cat script.sh
#!/bin/bash
# Using one command-line parameter
#
factorial=1
if [ -z $1 ]
then
echo "Input Format: ./script.sh number"
exit 1
fi
for (( number = 1; number <= $1; number++ ))
do
factorial=$[ $factorial * $number ]
done
echo The factorial of $1 is $factorial
exit
$ ./script.sh 5
The factorial of 5 is 120
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
提示
- 从
$1开始的参数都被称为 positional parameters (位置参数) - 脚本的参数之间使用 space 作为分隔,所以如果传入的某个位置参数中间有空格,需要使用引号扩起来表示这个一个参数 (可以使用单引号或者双引号)
- 如果传入的参数多余 9 个,参数对应的变量名需要使用 bracket (比如
${10})
# Tracking parameters
# Reading the scripts name
$0 存储的是命令行的第一个参数,即脚本的名称,但是不同输入格式的相同脚本,得到的名称是不一样的。
举例:
$ cat script.sh
#!/bin/bash
# Handling the $0 command-line parameter
#
echo This script name is $0.
$ ./script.sh
This script name is ./script.sh.
$ bash script.sh
This script name is script.sh.
2
3
4
5
6
7
8
9
10
解决办法,使用命令 basename (Remove leading directory portions from a path):
$ cat script.sh
#!/bin/bash
# Using basename with the $0 command-line parameter
# command substitution
name=$(basename $0)
#
echo This script name is $name.
exit
$ ./script.sh
This script name is script.sh.
2
3
4
5
6
7
8
9
10
11
# Testing parameters
在使用传入 script 的参数之前,应对判断输入的脚本参数数量是否符合要求:
不判断产生的错误:
$ cat script.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
exit
$ ./script.sh
./script.sh: line 5: ((: number <= : syntax error: operand expected (error token is "<= ")
The factorial of is 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
解决办法:使用test 或 [] 命令进行判断
$ cat script.sh
#!/bin/bash
# Using one command-line parameter
#
factorial=1
if [ -z $1 ] # 等价于 test -z $1
then
echo "Input Format: ./script.sh number"
exit 1
fi
for (( number = 1; number <= $1; number++ ))
do
factorial=$[ $factorial * $number ]
done
echo The factorial of $1 is $factorial
exit
$ ./script.sh 5
The factorial of 5 is 120
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Counting parameters $#
- 变量
#$中包含了脚本命令的所有 positional parameters 的数量 (即不包括脚本名$0)。
$ cat script.sh
#!/bin/bash
# Counting command-line parameters
#
case $# in
0) echo "no parameter was supplied";;
1) echo "$# parameter was supplied";;
*) echo "$# parameters were supplied";;
esac
$ ./script.sh
no parameter was supplied
$ ./script.sh 1
1 parameter was supplied
$ ./script.sh 1 3
2 parameter was supplied
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
通过 Counting Parameter 可以帮助我们检查用户使用脚本时传入的参数是否正确。
举例:
$ cat script.sh
#!/bin/bash
# Adding command-line parameters
#
if [ $# -ne 2 ]
then
echo Usage: $(basename $0) parameter1 parameter2
else
total=$[ $1 + $2 ]
echo $1 + $2 is $total
fi
exit
$ ./script.sh
Usage: script.sh parameter1 parameter2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 变量
{!#}中包含了传入脚本的最后一个参数:
$ cat script.sh
#!/bin/bash
# Testing grabbing the last parameter
#
echo The number of parameters is $#
echo The last parameter is ${!#}
exit
$ ./script.sh A B
The number of parameters is 2
The last parameter is B
$ ./script.sh
The number of parameters is 0
The last parameter is ./script.sh
2
3
4
5
6
7
8
9
10
11
12
13
14
# Grabbing all the data
# 四种形式 $@ $* "$@" "$*"
可以使用 $@ $* "$@" "$*" 四个变量来存储输入的参数 (positional parameters)。
区别: $@ $* "$@" "$*"
"$*" : 输入的所有参数变成了一个字符串
"$@" : 输入的每个参数都被作为一个字符串
$* : 输入的每个元素都当作一个 unquoted string (下面例子中最后一个字符串也被拆分为两个字符串)
$@ : 输入的每个元素都当作一个 unquoted string (下面例子中最后一个字符串也被拆分为两个字符串)
举例:
$ cat script.sh
#!/bin/bash
echo "Using \"\$*\":"
for a in "$*"; do
echo $a;
done
echo -e "\nUsing \$*:"
for a in $*; do
echo $a;
done
echo -e "\nUsing \"\$@\":"
for a in "$@"; do
echo $a;
done
echo -e "\nUsing \$@:"
for a in $@; do
echo $a;
done
$ ./script.sh one two "three four"
Using "$*":
one two three four
Using $*:
one
two
three
four
Using "$@":
one
two
three four
Using $@:
one
two
three
four
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
参考资料:
How to iterate over arguments in a Bash script (opens new window)
What's the difference between $@ and $* (opens new window)
# Being Shifty
shift 命令可以用来移动 positional parameters. 当使用 shift 命令时,默认情况下,它会将每个位置参数对应的变量向左移动一个位置。
$3 移动到 $2 (同时 $3 变量为空字符串); $2 移动到 $1 , $1 被丢弃,以此类推。
使用方法:使用在对命令行参数的另一种方法
$ cat script.sh
#!/bin/bash
# Shifting through the parameters
#
echo
echo "Using the shift method:"
count=1
while [ -n "$1" ]
do
echo "Parameter #$count = $1"
count=$[ $count + 1 ]
shift
done
echo
exit
$ ./script.sh alpha bravo charlie delta
Using the shift method:
Parameter #1 = alpha
Parameter #2 = bravo
Parameter #3 = charlie
Parameter #4 = delta
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
注意
使用 shift 移动后,丢弃的参数不可恢复。
shift n向左移动 n 个参数变量
$ cat script.sh
#!/bin/bash
# Shifting multiple positions through the parameters
#
echo
echo "The original parameters: $*"
echo "Now shifting 2..."
shift 2
echo "Here's the new parameter: $*"
echo
exit
$ ./script.sh alpha bravo charlie delta
The original parameters: alpha bravo charlie delta
Now shifting 2...
Here's the new parameter: charlie delta
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Working with Options
本节讲介绍三种使用 Options 的方法:
- Processing simple options
- Separating options from parameters
同处理命令行参数一样,也可以使用上面介绍的处理命令行参数的方法来处理 Options.
$ cat script.sh
#!/bin/bash
# Shifting through the parameters
#
echo
echo "Using the shift method:"
count=1
while [ -n "$1" ]
do
echo "Parameter #$count = $1"
count=$[ $count + 1 ]
shift
done
echo
exit%
$ ./script.sh -d x foo
Using the shift method:
Parameter #1 = -d
Parameter #2 = x
Parameter #3 = foo
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Processing simple options
可以使用 Grabbing all the data 中介绍的两种方法来获取每一个命令行参数,然后使用 case 进行判断该命令行参数是否是 option.
使用 shift + case:
$ cat script.sh
#!/bin/bash
# Extract command-line options
#
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%
$ ./script.sh -a -b -c
Found the -a option
Found the -b option
Found the -c option
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
使用 "$@" + case:
$ cat script.sh
#!/bin/bash
# Extract command-line options
#
echo
for var in "$@"
do
case "var" 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%
$ ./script.sh -a -b -c fasd
-a is not an option
-b is not an option
-c is not an option
fasd is not an option
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Separating options from parameters
在使用脚本时,经常会遇到既需要传入 options 又需要传入 parameters 的情况,在 Linux 中常使用 double dash -- 来表明 options 的结束和 parameters 的开始。处理方法如下面的例子所示:
$ cat script.sh
#!/bin/bash
# Extract command-line options and parameters
#
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
#
echo
count=1
for param in $@
do
echo "Parameter #$count: $param"
count=$[ $count + 1 ]
done
echo
exit
$ ./script.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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# Processing options with values
有时,某些 option 带有 value,处理方法:
$ cat script.sh
#!/bin/bash
# Extract command-line options and values
#
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
#
echo
count=1
for param in $@
do
echo "Parameter #$count: $param"
count=$[ $count + 1 ]
done
exit
$ ./script.sh -a -b Bvalue -c -- test1 test2 test3
Found the -a option
Found the -b option with parameter value Bvalue
Found the -c option
Parameter #1: test1
Parameter #2: test2
Parameter #3: test3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
但是这种方法适合于参数比较单一的情况,不适合处理复杂情况,比如有多个选项结合为一个选项:
$ ./script.sh -ac
-ac is not an option
2
3
解决办法: getopt
# getopt
(专门用来处理命令行选项和命令行参数的命令,在 C 语言中也有类似的命令用来处理 C 语言的程序的输入参数)
(不建议使用)
getopt 的使用方法:将 parameters 根据 optstring 转化为带参数和选项的命令行标准形式
getopt optstring parameters
其中 optstring 为需要处理的选项的定义,如果一个选项带有 value 就在选项后面加 : 。
使用举例:
# b:表示-b选项带有value
$ getopt ab:cd -a -b BValue -cd test1 test2
-a -b BValue -c -d -- test1 test2
2
3
如果 parameters 中加入了 optstring 中没有定义的选项会产生错误:
$ getopt ab:cd -a -b BValue -cde test1 test2
getopt: invalid option -- 'e'
-a -b BValue -c -d -- test1 test2
2
3
如果需要忽略错误在 getopt 中使用 -q 选项:
$ getopt -q ab:cd -a -b BValue -cde test1 test2
-a -b 'BValue' -c -d -- 'test1' 'test2'
2
(如上面的例子所示,getopt 的选项 ( -q ) 必须放在 optstring 的前面)
set -- 可以用来设置位置参数 (positional parameters):
$ myvar="This is a test"
$ set -- $myvar
$ echo $1
This
$ echo $2
is
$ echo $3
a
$ echo $4
test
2
3
4
5
6
7
8
9
10
11
(注意只在 bash shell 中有效,其他 shell 中不能保证一定可用)
从而使用一点 trick (使用 set -- 来分割 getopt 得到的字符串,然后存入 positonal parameters 中),就可以解析命令行参数:
set -- $(getopt -q ab:cd "$@")
具体实现:
$ cat script.sh
#!/bin/bash
# Extract command-line options and values with getopt
#
set -- $(getopt -q ab:cd "$@")
#
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
#
echo
count=1
for param in $@
do
echo "Parameter #$count: $param"
count=$[ $count + 1 ]
done
exit
❯ ./script.sh -c -d -b BValue -a test1 test2
Found the -c option
-d is not an option
Found the -b option with parameter value
Found the -a option
Parameter #1: 'test1'
Parameter #2: 'test2'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
但是 getopt 在处理带有引号的参数时,存在一些问题:
在上面的 script.sh 中运行:
$ ./script.sh -c -d -b BValue -a "test1 test2"
Found the -c option
-d is not an option
Found the -b option with parameter value shift
'BValue' is not an option
Found the -a option
Parameter #1: 'test1
Parameter #2: test2'
2
3
4
5
6
7
8
9
10
问题存在的原因:
getopt 会把引号中空格分隔的参数当作多个参数来解释,举例说明:
$ cat script.sh
#!/bin/bash
# Extract command-line options and values with getopt
#
echo "\"@\":"
for item in "$@"
do
echo $item
done
echo
echo "use getopt:"
var=$(getopt -q ab:cd "$@")
for item in $var
do
echo $item
done
$ ./script.sh -c -d -b BValue -a 'test1 test2'
"@":
-c
-d
-b
BValue
-a
test1 test2
use getopt:
-c
-d
-b
'BValue'
-a
--
'test1
test2'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
解决办法:使用 getopts
# getopts
(建议使用)
getopt 一次性解释出所有的选项和参数,而 getopts 是循环解释,每次只解释一个选项或者参数。
格式:
getopts optstring variable
getopts 有两个环境变量:
OPTARG: 如果一个选项有 value 时,value 就存储在OPTARG中OPTIND:OPTIND是要一个要处理参数 a 的 index,从一开始- 在 optstring 开头使用
:用来抑制错误信息(类似于 getopt 的 - q 命令用来忽略输入参数在 optstring 中没有定义产生错误) - 选项后面的
:表示该选项有 value - 通过 getopts 进行解释 case 中的选项不需要 leading dash
举例:
$ cat script.sh
#!/bin/bash
# Extract command-line options and parameters with getopts
#
echo
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
$ ./script.sh -b "BValue1 BValue2" -a
Found the -b option with parameter value BValue1 BValue2
Found the -a option
$ ./script.sh -abBValue
Found the -a option
Found the -b option with parameter value BValue
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
OPTIND :
理解 OPTIND 的工作原理:
工作原理见: How does the OPTIND variable work in the shell builtin getopts (opens new window)
使用举例:移除所有的选项,打印参数
$ cat script.sh
#!/bin/bash
# Extract command-line options and parameters with getopts
#
echo
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
#
shift $[ $OPTIND - 1 ]
#
echo
count=1
for param in "$@"
do echo "Parameter $count: $param"
count=$[ $count + 1 ]
done
exit
$ ./script.sh -b "BValue1 BValue2" -a test1 "test2 test3"
Found the -b option with parameter value BValue1 BValue2
Found the -a option
Parameter 1: test1
Parameter 2: test2 test3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Common Linux Command-Line Options
命令行中下面这些选项的含义是最长使用的,在写脚本时,也应该将自己的脚本选项这样定义:
| Option | Description |
|---|---|
| -a | Shows all objects. |
| -c | Produces a count. |
| -d | Specifies a directory. |
| -e | Expands an object. |
| -f | Specifies a file to read data from. |
| -h | Displays a help message for the command. |
| -i | Ignores text case. |
| -l | Produces a long format version of the output. |
| -n | Uses a non-interactive (batch) mode. |
| -o | Specifies a file to which all output is redirected. |
| -q | Runs in quiet mode. |
| -r | Processes directories and files recursively. |
| -s | Runs in silent mode. |
| -v | Produces verbose output. |
| -x | Excludes an object. |
| -y | Answers yes to all questions. |
# Getting User Input
有时在脚本运行过程中也会出现需要用户输入的情况,此时可以使用 read 命令来读取用户的输入。
# Reading Basics
read 命令可以从标准输入 (通常来说是键盘) 和文件中读取值作为变量:
-p命令可以将提示语句直接放在 read 命令中
$ cat script.sh
#!/bin/bash
# Using the read command
# -n means do not print the trailing newline character.
echo -n "Enter your name: "
read name # 与上面一句一起等价于: read -p "Enter your name: " name
echo "Hello $name, welcome to my script."
exit
$ ./script.sh
Enter your name: ningxu
Hello ningxu, welcome to my script.
$ ./script.sh
Enter your name: ning xu
Hello ning xu, welcome to my script.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
read命令将所有的输入都赋值给变量,如果read输入的值数量比变量数量多,那么多余的值都赋值给最后一个变量
$ cat script.sh
#!/bin/bash
# Using the read command
#
read -p "Enter your name: " first last
echo "Hello $first, $last welcome to my script."
exit%
$ ./script.sh
Enter your name: king song en
Hello king, song en welcome to my script.
2
3
4
5
6
7
8
9
10
11
- 如果没有给
read命令指定 value 存放的变量,那么所有的 value 都会存到环境变量REPLY中
$ cat script.sh
#!/bin/bash
# Using the read command
#
read -p "Enter your name: "
echo "Hello $REPLY welcome to my script."
exit
$ ./script.sh
Enter your name: ning xu
Hello ning xu welcome to my script.
2
3
4
5
6
7
8
9
10
11
12
-t命令指定等待输入的时间-n命令指定输入字符的数量,如果输入数量达到指定数量,不需要回车键,自动运行-s命令指定输入会被隐藏,常用在输入密码时
# Reading from file
read 命令每次可以从文件中读取一行输入,因此可以使用 read 命令来读取文件:
$ cat 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?
$ cat script.sh
#!/bin/bash
# Using the read command to read a file
#
count=1
# 当读取完文件的所有行时,read命令返回非0状态码,程序结束运行
cat ./test.txt | while read line
do
echo "Line $count: $line"
count=$[ $count + 1 ]
done
echo "Finished processing the file."
$ ./script.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.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23