tartarus's bolg tartarus's bolg
  • Linux and Unix Guide
  • CMake
  • gcc
  • gdb
  • bash
  • GNU Make
  • DDCA-ETH
  • CS106L
  • CS144
  • NJU PA
  • NJU OS(jyy)
  • C
  • C++
  • Python
  • reveal-md
  • LaTex
  • Paper Reading
  • TBD
  • Linux and Unix Guide
  • CMake
  • gcc
  • gdb
  • bash
  • GNU Make
  • DDCA-ETH
  • CS106L
  • CS144
  • NJU PA
  • NJU OS(jyy)
  • C
  • C++
  • Python
  • reveal-md
  • LaTex
  • Paper Reading
  • TBD
  • pdb

  • make

  • cmake

  • Linux and Unix

    • Linux Tool介绍
    • Useful Linux Tool
    • regular-expression
    • Basic-Shell-Script
    • Using-Structured-Commands
    • More-Structured-Commands
    • Handling-User-Input
      • Passing Parameters
        • Command Line Parameters
      • Tracking parameters
        • Reading the scripts name
        • Testing parameters
        • Counting parameters$#
      • Grabbing all the data
        • 四种形式$@ $* "$@" "$*"
        • Being Shifty
      • Working with Options
        • Processing simple options
        • Separating options from parameters
        • Processing options with values
        • getopt
        • getopts
        • Common Linux Command-Line Options
      • Getting User Input
        • Reading Basics
        • Reading from file
    • Presenting-Data
    • Script-Control
  • Basic_Software
  • Linux and Unix
tartarus
2023-05-26
目录

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
1
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.
1
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.
1
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
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
1
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
1
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
1
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
1
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
1
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
1
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
1
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
1
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
1
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
1
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

1
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
1
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
1
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
1
2
3

如果 parameters 中加入了 optstring 中没有定义的选项会产生错误:

$ getopt ab:cd -a -b BValue -cde test1 test2
getopt: invalid option -- 'e'
 -a -b BValue -c -d -- test1 test2
1
2
3

如果需要忽略错误在 getopt 中使用 -q 选项:

$ getopt -q ab:cd -a -b BValue -cde test1 test2
 -a -b 'BValue' -c -d -- 'test1' 'test2'
1
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
1
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'
1
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'
1
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'
1
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
1

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
1
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
1
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.
1
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.
1
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.
1
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
上次更新: 12/27/2023, 8:55:47 AM
More-Structured-Commands
Presenting-Data

← More-Structured-Commands Presenting-Data→

Theme by Vdoing | Copyright © 2023-2023 tartarus | CC BY-NC-SA 4.0
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式