编写 Linux Shell 脚本处理命令行参数

如果不会用 Linux 文本编辑器 nano / vim 等,请自行了解相关用法。
如果是新建文件,默认应该没有执行权限,需要 chmod +x 文件名 添加权限。

原生#

$0 / $1 / $2 / $3 ... $##

新建或编辑 test_args.sh,输入以下内容并保存:

1
2
3
4
5
6
7
#!/bin/bash

echo "执行的目标: $0"
echo "第一个参数为:$1"
echo "第二个参数为:$2"
echo "第三个参数为:$3"
echo "参数个数为:$#"

附带参数运行该脚本:

$ ./test_args.sh apple banana candy

执行的目标: ./test_args.sh
第一个参数为:apple
第二个参数为:banana
第三个参数为:candy
参数个数为:3

$@ / $*#

新建或编辑 test_args.sh,输入以下内容并保存:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/bin/bash

echo "全部参数组成一个数组:[$@]"
for item in "$@"
do
    echo $item
done

echo "全部参数组成一个字符串: \"$*\""
for item in "$*"
do
    echo $item
done

附带参数运行该脚本:

$ ./test_args.sh apple banana candy

全部参数组成一个数组:[apple banana candy]
apple
banana
candy
全部参数组成一个字符串: "apple banana candy"
apple banana candy

$$ / $! / $?#

新建或编辑 test_args.sh,输入以下内容并保存:

1
2
3
4
5
6
#!/bin/bash

nohup sleep 5 >/dev/null 2>&1 &
echo "此脚本运行的当前进程 pid: $$"
echo "后台运行的最后一个进程的 pid: $!"
echo "上一条命令是否执行成功: $? (0=成功 非0=失败)"

附带参数运行该脚本:

$ ./test_args.sh

此脚本运行的当前进程 pid: 4255
后台运行的最后一个进程的 pid: 4256
上一条命令是否执行成功: 0 (0=成功 非0=失败)

$_#

新建或编辑 test_args.sh,输入以下内容并保存:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/bin/bash

echo "此脚本文件名: $_"
echo "maybe" "next" "time"
echo "上一条命令(echo)的最后一个参数: $_"

echo ""

let foo=123
echo "上一条命令(let)的最后一个参数: \"$_\""
echo $foo
echo "上一条命令(echo)的最后一个参数: $_(此处直接取其值 而不是原样打印变量名)"

附带参数运行该脚本:

$ ./test_args.sh

此脚本文件名: /root/era_games_data/./test_args.sh
maybe next time
上一条命令(echo)的最后一个参数: time

上一条命令(let)的最后一个参数: "foo=123"
123
上一条命令(echo)的最后一个参数: 123(此处直接取其值 而不是原样打印变量名)

$-#

新建或编辑 test_args.sh,输入以下内容并保存:

1
2
3
#!/bin/bash

echo "打印当前 shell 的选项: $-"

附带参数运行该脚本:

$ ./test_args.sh

打印当前 shell 的选项: hB
  • H - histexpand:已启用 历史记录扩展History Expansion
  • m - monitor:已启用 作业控制Job Control
  • h - hashall:允许记住命令以便之后查找并再次运行
  • B - braceexpand:已启用 花括号扩展Brace Expansion
  • i - interactive:当前 shell 是可交互的

功能稍弱,用法也更简单 - getopts#

用法说明#

  • 只能在 shell 脚本中使用,不能在命令行中单独使用
  • 只支持形如 -p 的短格式,不支持形如 --port 的长格式
  • 要么包含具体的参数值(如 -p 80),要么不包含(如 -h),只能二选一
    (注意这里只是随便举的例,你完全可以设定 -p 不包含参数值,而 -h 包含)
  • 如果包含具体的参数值:
    • 既可以用空格隔开(如 -p 80),也可以紧接其后(如 -p80
    • 只提供了参数却没有提供具体值时(如 -p)会直接报错

实际举例#

新建或编辑 test_args.sh,输入以下内容并保存:

 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
#!/bin/bash

print_usage() {
    echo "参数用法:
    -i    指定 IP
    -p    指定端口
    -h    显示帮助信息"
}

main() {
    while getopts "i:p:h" args; do
        case $args in
        i)
            ip="$OPTARG"
            echo "ip:   $ip"
            ;;
        p)
            port="$OPTARG"
            echo "port: $port"
            ;;
        h)
            print_usage
            exit
            ;;
        ?)
            echo "error: 无法识别的参数"
            exit 1
            ;;
        esac
    done
}

main $@

这里 getopts 的参数格式为 i:p:h,可以分割为 i: + p: + h
: 的意思是「需要指定具体的参数值」,没带 : 的就不用。

测试用例#

运行该脚本:

$ ./test_args.sh -h

参数用法:
    -i    指定 IP
    -p    指定端口
    -h    显示帮助信息
$ ./test_args.sh -i 127.0.0.1 -p 80

ip:   127.0.0.1
port: 80
$ ./test_args.sh -i192.168.1.1 -p80

ip:   192.168.1.1
port: 80

功能更强,用法也更复杂 - getopt#

没有写反,getopt(不带 s)就是比带 sgetopts 功能更多、更复杂。

用法说明#

  • 既可以在 shell 脚本中使用,又能在命令行中单独使用
  • 既支持形如 -p 的短格式,又支持形如 --port 的长格式
  • 可以同时兼容包含具体的参数值(如 -p 80),以及不包含(如 -p)的情况
  • 如果包含具体的参数值:
    • 形如 -p 的短格式:
      既可以用空格隔开(如 -p 80),也可以紧接其后(如 -p80
    • 形如 --port 的长格式:
      可以用空格(如 --port 80)或等号(如 --port=80)隔开

实际举例#

新建或编辑 test_args.sh,输入以下内容并保存:

 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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#!/bin/bash

print_usage() {
    echo "参数用法:

    -i, --ip
            指定 IP

    -p, --port
            指定端口

    -h, --help
            显示帮助信息"
}

main() {
    ARGS=$(getopt --options i:p::h --long ip:,port::,help -name 'getopt用法示例脚本' -- "$@")
    if [ $? != 0 ]; then
        echo "error: 使用 getopt 解析参数时发生错误"
        exit 1
    fi

    eval set -- "${ARGS}"
    while true; do
        case "$1" in
        -i | --ip)
            echo "ip:   $2"
            shift 2
            ;;
        -p | --port)
            case "$2" in
            "")
                echo "port: 没有指定具体值(此时可以另行处理,比如默认为80)"
                shift 2
                ;;
            *)
                echo "port: $2"
                shift 2
                ;;
            esac
            ;;
        -h | --help)
            print_usage
            exit
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "error: 内部处理出错"
            exit 1
            ;;
        esac
    done

    for arg in $@; do
        echo "剩余未处理的参数: $arg"
    done
}

main $@

至于 getopt 的参数格式,首先 getopt 本身有三个参数:

  • -o / --options:支持形如 -p 的短格式
    比如 i:p::h,可以分割为 i: + p::+ h
    getopts 多出一个带 :: 的格式,意思是可选是否指定具体值
  • -l / --long:支持形如 --port 的长格式
    冒号 : 的意义同短格式,不过多个参数之间需要用逗号 , 分开
    比如 ip:,port::,help,可以分割为 ip:port::help
  • -n / -name:指定解析错误时提示的脚本名
  • 更多参数用法详见 getopt -h

测试用例#

运行该脚本:

$ ./test_args.sh -h

参数用法:

    -i, --ip
            指定 IP

    -p, --port
            指定端口

    -h, --help
            显示帮助信息
$ ./test_args.sh -i127.0.0.1 -p80 input output

ip:   127.0.0.1
port: 80
剩余未处理的参数: input
剩余未处理的参数: output
$ ./test_args.sh -i 192.168.1.1 -p 80 input output
# 由于port可以不指定具体值 所以后面的80会被当作孤立的另外一个参数

ip:   192.168.1.1
port: 没有指定具体值(此时可以另行处理,比如默认为80)
剩余未处理的参数: 80
剩余未处理的参数: input
剩余未处理的参数: output
$ ./test_args.sh --ip=0.0.0.0 --port=8080 input output

ip:   0.0.0.0
port: 8080
剩余未处理的参数: input
剩余未处理的参数: output
$ ./test_args.sh --ip 0.0.0.0 --port 8080 input output
# 由于port可以不指定具体值 所以后面的8080会被当作孤立的另外一个参数

ip:   0.0.0.0
port: 没有指定具体值(此时可以另行处理,比如默认为80)
剩余未处理的参数: 8080
剩余未处理的参数: input
剩余未处理的参数: output

lackbfun © 2021 - 2024