Bash命令行参数解析方法

Bash命令行参数解析方法

技术背景

在Bash脚本编程中,解析命令行参数是一项常见的需求。通过解析命令行参数,脚本可以根据用户的输入动态调整其行为。不同的参数格式和解析方法适用于不同的场景,掌握这些方法可以提高脚本的灵活性和可维护性。

实现步骤

1. 空格分隔参数解析

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

POSITIONAL_ARGS=()

while [[ $# -gt 0 ]]; do
case $1 in
-e|--extension)
EXTENSION="$2"
shift # past argument
shift # past value
;;
-s|--searchpath)
SEARCHPATH="$2"
shift # past argument
shift # past value
;;
--default)
DEFAULT=YES
shift # past argument
;;
-*|--*)
echo "Unknown option $1"
exit 1
;;
*)
POSITIONAL_ARGS+=("$1") # save positional arg
shift # past argument
;;
esac
done

set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters

echo "FILE EXTENSION = ${EXTENSION}"
echo "SEARCH PATH = ${SEARCHPATH}"
echo "DEFAULT = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)

if [[ -n $1 ]]; then
echo "Last line of file specified as non-opt/last argument:"
tail -1 "$1"
fi

2. 等号分隔参数解析

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

for i in "$@"; do
case $i in
-e=*|--extension=*)
EXTENSION="${i#*=}"
shift # past argument=value
;;
-s=*|--searchpath=*)
SEARCHPATH="${i#*=}"
shift # past argument=value
;;
--default)
DEFAULT=YES
shift # past argument with no value
;;
-*|--*)
echo "Unknown option $i"
exit 1
;;
*)
;;
esac
done

echo "FILE EXTENSION = ${EXTENSION}"
echo "SEARCH PATH = ${SEARCHPATH}"
echo "DEFAULT = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)

if [[ -n $1 ]]; then
echo "Last line of file specified as non-opt/last argument:"
tail -1 $1
fi

3. 使用getopt[s]解析

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
#!/bin/bash
# More safety, by turning some bugs into errors.
set -o errexit -o pipefail -o noclobber -o nounset

# ignore errexit with `&& true`
getopt --test > /dev/null && true
if [[ $? -ne 4 ]]; then
echo 'I’m sorry, `getopt --test` failed in this environment.'
exit 1
fi

# option --output/-o requires 1 argument
LONGOPTS=debug,force,output:,verbose
OPTIONS=dfo:v

# -temporarily store output to be able to check for errors
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
# -pass arguments only via -- "$@" to separate them correctly
# -if getopt fails, it complains itself to stderr
PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@") || exit 2
# read getopt’s output this way to handle the quoting right:
eval set -- "$PARSED"

d=n f=n v=n outFile=-
# now enjoy the options in order and nicely split until we see --
while true; do
case "$1" in
-d|--debug)
d=y
shift
;;
-f|--force)
f=y
shift
;;
-v|--verbose)
v=y
shift
;;
-o|--output)
outFile="$2"
shift 2
;;
--)
shift
break
;;
*)
echo "Programming error"
exit 3
;;
esac
done

# handle non-option arguments
if [[ $# -ne 1 ]]; then
echo "$0: A single input file is required."
exit 4
fi

echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"

4. 使用POSIX getopts解析

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

# A POSIX variable
OPTIND=1 # Reset in case getopts has been used previously in the shell.

# Initialize our own variables:
output_file=""
verbose=0

while getopts "h?vf:" opt; do
case "$opt" in
h|\?)
show_help
exit 0
;;
v) verbose=1
;;
f) output_file=$OPTARG
;;
esac
done

shift $((OPTIND-1))

[ "${1:-}" = "--" ] && shift

echo "verbose=$verbose, output_file='$output_file', Leftovers: $@"

核心代码

以下是几种常见解析方法的核心代码片段:

空格分隔参数解析核心代码

1
2
3
4
5
6
7
while [[ $# -gt 0 ]]; do
case $1 in
# 处理不同参数
...
esac
shift
done

等号分隔参数解析核心代码

1
2
3
4
5
6
for i in "$@"; do
case $i in
# 处理不同参数
...
esac
done

getopt[s]解析核心代码

1
2
3
4
5
6
7
8
9
PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@") || exit 2
eval set -- "$PARSED"

while true; do
case "$1" in
# 处理不同参数
...
esac
done

getopts解析核心代码

1
2
3
4
5
6
7
while getopts "h?vf:" opt; do
case "$opt" in
# 处理不同参数
...
esac
done
shift $((OPTIND-1))

最佳实践

  • 选择合适的解析方法:根据参数的格式和需求选择合适的解析方法。如果需要支持长选项和更复杂的参数格式,getoptgetopts是不错的选择;如果只是简单的参数解析,手动处理也是可行的。
  • 错误处理:在解析过程中,要对未知参数和缺少参数的情况进行处理,避免脚本因参数错误而崩溃。
  • 代码可读性:使用case语句和清晰的注释可以提高代码的可读性和可维护性。

常见问题

1. getopt版本问题

older版本的getopt存在一些限制,如不能处理空字符串参数和包含空格的参数。较新的版本已经解决了这些问题。

2. getopts不支持长选项

getopts默认只支持短选项,如果需要支持长选项,需要额外的代码来处理。

3. 参数格式错误

如果用户输入的参数格式不符合脚本的要求,可能会导致解析错误。在脚本中要对参数格式进行检查和处理。


Bash命令行参数解析方法
https://119291.xyz/posts/2025-05-13.bash-command-line-argument-parsing-methods/
作者
ww
发布于
2025年5月13日
许可协议