比如,我有一个脚本,用这行代码调用:
./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
或者这个:
./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile
什么是公认的解析方式,以便在每种情况下(或两者的某种组合)$v、$f和$d都将设置为true,而$outFile将等于/fiz/someOtherFile?
比如,我有一个脚本,用这行代码调用:
./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
或者这个:
./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile
什么是公认的解析方式,以便在每种情况下(或两者的某种组合)$v、$f和$d都将设置为true,而$outFile将等于/fiz/someOtherFile?
当前回答
我发现在脚本中编写可移植解析的问题非常令人沮丧,因此我编写了Argbash-一个FOSS代码生成器,它可以为您的脚本生成参数解析代码,并且具有一些不错的功能:
https://argbash.io
其他回答
Bash空格分隔(例如--option参数)
cat >/tmp/demo-space-separated.sh <<'EOF'
#!/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
EOF
chmod +x /tmp/demo-space-separated.sh
/tmp/demo-space-separated.sh -e conf -s /etc /etc/hosts
复制粘贴上面的块的输出
FILE EXTENSION = conf
SEARCH PATH = /etc
DEFAULT =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34 example.com
用法
demo-space-separated.sh -e conf -s /etc /etc/hosts
Bash等于分隔符(例如,--option=argument)
cat >/tmp/demo-equals-separated.sh <<'EOF'
#!/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
EOF
chmod +x /tmp/demo-equals-separated.sh
/tmp/demo-equals-separated.sh -e=conf -s=/etc /etc/hosts
复制粘贴上面的块的输出
FILE EXTENSION = conf
SEARCH PATH = /etc
DEFAULT =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34 example.com
用法
demo-equals-separated.sh -e=conf -s=/etc /etc/hosts
要更好地理解${i#*=},请在本指南中搜索“Substring Removal”。它在功能上等同于调用一个不需要的子流程的‘sed’s/[^=]*=//'<<<“$i”`或调用两个不需要子流程的“echo”$i”|sed’s/[^=]*=//'`。
将bash与getopt[s]一起使用
getopt(1)限制(较旧、较新的getopt版本):
无法处理空字符串的参数无法处理带有嵌入空格的参数
最近的getopt版本没有这些限制。有关更多信息,请参阅这些文档。
POSIX获取选项
此外,POSIX外壳和其他提供的getopts没有这些限制。我包含了一个简单的getopts示例。
cat >/tmp/demo-getopts.sh <<'EOF'
#!/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: $@"
EOF
chmod +x /tmp/demo-getopts.sh
/tmp/demo-getopts.sh -vf /etc/hosts foo bar
复制粘贴上面的块的输出
verbose=1, output_file='/etc/hosts', Leftovers: foo bar
用法
demo-getopts.sh -vf /etc/hosts foo bar
getopts的优点是:
它更便于携带,可以在其他外壳中使用,如dash。它可以以典型的Unix方式自动处理多个单一选项,如-vf文件名。
getopts的缺点是它只能处理短选项(-h,而不是-help),而无需额外代码。
有一个getopts教程,它解释了所有语法和变量的含义。在bash中,还有帮助getopts,这可能会提供信息。
这也可能很有用:您可以设置一个值,如果有人提供输入,则使用该值覆盖默认值。
myscript.sh-f/serverlist.txt或只是/myscript.sh(它采用默认值)
#!/bin/bash
# --- set the value, if there is inputs, override the defaults.
HOME_FOLDER="${HOME}/owned_id_checker"
SERVER_FILE_LIST="${HOME_FOLDER}/server_list.txt"
while [[ $# > 1 ]]
do
key="$1"
shift
case $key in
-i|--inputlist)
SERVER_FILE_LIST="$1"
shift
;;
esac
done
echo "SERVER LIST = ${SERVER_FILE_LIST}"
我使用optget和optgets的组合来解析带或不带参数的短选项和长选项,甚至是非选项(不带-或-的选项):
# catch wrong options and move non-options to the end of the string
args=$(getopt -l "$opt_long" "$opt_short" "$@" 2> >(sed -e 's/^/stderr/g')) || echo -n "Error: " && echo "$args" | grep -oP "(?<=^stderr).*" && exit 1
mapfile -t args < <(xargs -n1 <<< "$(echo "$args" | sed -E "s/(--[^ ]+) /\1=/g")" )
set -- "${args[@]}"
# parse short and long options
while getopts "$opt_short-:" opt; do
...
done
# remove all parsed options from $@
shift $((OPTIND-1)
这样,我就可以使用$opt_verbose这样的变量访问所有选项,而非选项可以通过默认变量$1、$2等访问:
echo "help:$opt_help"
echo "file:$opt_file"
echo "verbose:$opt_verbose"
echo "long_only:$opt_long_only"
echo "short_only:$opt_s"
echo "path:$1"
echo "mail:$2"
其中一个主要特点是,我能够以完全随机的顺序传递所有选项和非选项:
# $opt_file $1 $2 $opt_... $opt_... $opt_...
# /demo.sh --file=file.txt /dir info@example.com -V -h --long_only=yes -s
help:1
file:file.txt
verbose:1
long_only:yes
short_only:1
path:/dir
mail:info@example.com
更多详情:https://stackoverflow.com/a/74275254/318765
根据这里的其他答案,这是我的版本:
#!/bin/bash
set -e
function parse() {
for arg in "$@"; do # transform long options to short ones
shift
case "$arg" in
"--name") set -- "$@" "-n" ;;
"--verbose") set -- "$@" "-v" ;;
*) set -- "$@" "$arg"
esac
done
while getopts "n:v" optname # left to ":" are flags that expect a value, right to the ":" are flags that expect nothing
do
case "$optname" in
"n") name=${OPTARG} ;;
"v") verbose=true ;;
esac
done
shift "$((OPTIND-1))" # shift out all the already processed options
}
parse "$@"
echo "hello $name"
if [ ! -z $verbose ]; then echo 'nice to meet you!'; fi
用法:
$ ./parse.sh
hello
$ ./parse.sh -n YOUR_NAME
hello YOUR_NAME
$ ./parse.sh -n YOUR_NAME -v
hello YOUR_NAME
nice to meet you!
$ ./parse.sh -v -n YOUR_NAME
hello YOUR_NAME
nice to meet you!
$ ./parse.sh -v
hello
nice to meet you!
另一个选项解析器(生成器)
用于shell脚本的优雅选项解析器(完全支持所有POSIX shell)https://github.com/ko1nksm/getoptions(更新:v3.3.0于2021-05-02发布)
getoptions是一个新的选项解析器(生成器),用POSIX兼容的shell脚本编写,于2020年8月发布。它适用于那些希望在shell脚本中支持POSIX/GNU样式选项语法的人。
支持的语法有-a、+a、-abc、-vvv、-p VALUE、-pVALUE、--flag、--no flag、--带flag、---不带flag,--param VALUE,--param=VALUE,--option[=VALUE],--no option--。
它支持子命令、验证、缩写选项和自动帮助生成。并且适用于所有POSIX shell(dash 0.5.4+、bash 2.03+、ksh88+、mksh R28+、zsh 3.1.9+、yash 2.29+、busybox ash 1.1.3+等)。
#!/bin/sh
VERSION="0.1"
parser_definition() {
setup REST help:usage -- "Usage: example.sh [options]... [arguments]..." ''
msg -- 'Options:'
flag FLAG -f --flag -- "takes no arguments"
param PARAM -p --param -- "takes one argument"
option OPTION -o --option on:"default" -- "takes one optional argument"
disp :usage -h --help
disp VERSION --version
}
eval "$(getoptions parser_definition) exit 1"
echo "FLAG: $FLAG, PARAM: $PARAM, OPTION: $OPTION"
printf '%s\n' "$@" # rest arguments
它解析以下参数:
example.sh -f --flag -p VALUE --param VALUE -o --option -oVALUE --option=VALUE 1 2 3
以及自动生成帮助。
$ example.sh --help
Usage: example.sh [options]... [arguments]...
Options:
-f, --flag takes no arguments
-p, --param PARAM takes one argument
-o, --option[=OPTION] takes one optional argument
-h, --help
--version
它也是一个选项解析器生成器,生成以下简单的选项解析代码。如果使用生成的代码,则不需要getoptions。实现真正的可移植性和零依赖性。
FLAG=''
PARAM=''
OPTION=''
REST=''
getoptions_parse() {
OPTIND=$(($#+1))
while OPTARG= && [ $# -gt 0 ]; do
case $1 in
--?*=*) OPTARG=$1; shift
eval 'set -- "${OPTARG%%\=*}" "${OPTARG#*\=}"' ${1+'"$@"'}
;;
--no-*|--without-*) unset OPTARG ;;
-[po]?*) OPTARG=$1; shift
eval 'set -- "${OPTARG%"${OPTARG#??}"}" "${OPTARG#??}"' ${1+'"$@"'}
;;
-[fh]?*) OPTARG=$1; shift
eval 'set -- "${OPTARG%"${OPTARG#??}"}" -"${OPTARG#??}"' ${1+'"$@"'}
OPTARG= ;;
esac
case $1 in
'-f'|'--flag')
[ "${OPTARG:-}" ] && OPTARG=${OPTARG#*\=} && set "noarg" "$1" && break
eval '[ ${OPTARG+x} ] &&:' && OPTARG='1' || OPTARG=''
FLAG="$OPTARG"
;;
'-p'|'--param')
[ $# -le 1 ] && set "required" "$1" && break
OPTARG=$2
PARAM="$OPTARG"
shift ;;
'-o'|'--option')
set -- "$1" "$@"
[ ${OPTARG+x} ] && {
case $1 in --no-*|--without-*) set "noarg" "${1%%\=*}"; break; esac
[ "${OPTARG:-}" ] && { shift; OPTARG=$2; } || OPTARG='default'
} || OPTARG=''
OPTION="$OPTARG"
shift ;;
'-h'|'--help')
usage
exit 0 ;;
'--version')
echo "${VERSION}"
exit 0 ;;
--)
shift
while [ $# -gt 0 ]; do
REST="${REST} \"\${$(($OPTIND-$#))}\""
shift
done
break ;;
[-]?*) set "unknown" "$1"; break ;;
*)
REST="${REST} \"\${$(($OPTIND-$#))}\""
esac
shift
done
[ $# -eq 0 ] && { OPTIND=1; unset OPTARG; return 0; }
case $1 in
unknown) set "Unrecognized option: $2" "$@" ;;
noarg) set "Does not allow an argument: $2" "$@" ;;
required) set "Requires an argument: $2" "$@" ;;
pattern:*) set "Does not match the pattern (${1#*:}): $2" "$@" ;;
notcmd) set "Not a command: $2" "$@" ;;
*) set "Validation error ($1): $2" "$@"
esac
echo "$1" >&2
exit 1
}
usage() {
cat<<'GETOPTIONSHERE'
Usage: example.sh [options]... [arguments]...
Options:
-f, --flag takes no arguments
-p, --param PARAM takes one argument
-o, --option[=OPTION] takes one optional argument
-h, --help
--version
GETOPTIONSHERE
}