比如,我有一个脚本,用这行代码调用:
./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?
当前回答
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,这可能会提供信息。
其他回答
while [ "$#" -gt 0 ]; do
case "$1" in
-n) name="$2"; shift 2;;
-p) pidfile="$2"; shift 2;;
-l) logfile="$2"; shift 2;;
--name=*) name="${1#*=}"; shift 1;;
--pidfile=*) pidfile="${1#*=}"; shift 1;;
--logfile=*) logfile="${1#*=}"; shift 1;;
--name|--pidfile|--logfile) echo "$1 requires an argument" >&2; exit 1;;
-*) echo "unknown option: $1" >&2; exit 1;;
*) handle_argument "$1"; shift 1;;
esac
done
此解决方案:
句柄-n arg和--name=arg允许在末尾使用参数如果有任何拼写错误,则显示正常错误兼容,不使用抨击可读,不需要在循环中维护状态
简单易修改,参数可以按任意顺序排列。这可以修改为采用任何形式的参数(-a、-a、a等)。
for arg in "$@"
do
key=$(echo $arg | cut -f1 -d=)`
value=$(echo $arg | cut -f2 -d=)`
case "$key" in
name|-name) read_name=$value;;
id|-id) read_id=$value;;
*) echo "I dont know what to do with this"
ease
done
来自digitalpeer.com,稍作修改:
用法myscript.sh-p=my_prefix-s=dirname-l=libname
#!/bin/bash
for i in "$@"
do
case $i in
-p=*|--prefix=*)
PREFIX="${i#*=}"
;;
-s=*|--searchpath=*)
SEARCHPATH="${i#*=}"
;;
-l=*|--lib=*)
DIR="${i#*=}"
;;
--default)
DEFAULT=YES
;;
*)
# unknown option
;;
esac
done
echo PREFIX = ${PREFIX}
echo SEARCH PATH = ${SEARCHPATH}
echo DIRS = ${DIR}
echo DEFAULT = ${DEFAULT}
要更好地理解${i#*=},请在本指南中搜索“Substring Removal”。它在功能上等同于调用一个不需要的子流程的‘sed’s/[^=]*=//'<<<“$i”`或调用两个不需要子流程的“echo”$i”|sed’s/[^=]*=//'`。
注意,getopt(1)是AT&T的一个短暂错误。
getopt创建于1984年,但1986年就已经被埋没了,因为它实际上并不可用。
getopt非常过时的一个证据是,getopt(1)手册页仍然提到“$*”而不是“$@”,这是1986年与getopts(1)Shell内置程序一起添加到Bourne Shell中的,目的是处理带有空格的参数。
顺便说一句:如果您对解析shell脚本中的长选项感兴趣,那么可能需要知道libc(Solaris)和ksh93中的getopt(3)实现都添加了一个统一的长选项实现,该实现支持长选项作为短选项的别名。这使得ksh93和BourneShell通过getopts为长选项实现了统一的接口。
Bourne Shell手册页中的长选项示例:
getopts“f:(file)(输入文件)o:(输出文件)”OPTX“$@”
显示了Bourne Shell和ksh93中可以使用多长时间的选项别名。
请参阅最近Bourne Shell的手册页:
http://schillix.sourceforge.net/man/man1/bosh.1.html
以及OpenSolaris的getopt(3)手册页:
http://schillix.sourceforge.net/man/man3c/getopt.3c.html
最后,使用getopt(1)手册页验证过时的$*:
http://schillix.sourceforge.net/man/man1/getopt.1.html
当我尝试这个问题时,上面的答案似乎有点bug——这是我发现更强大的解决方案:
boolean_arg=""
arg_with_value=""
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
-b|--boolean-arg)
boolean_arg=true
shift
;;
-a|--arg-with-value)
arg_with_value="$2"
shift
shift
;;
-*)
echo "Unknown option: $1"
exit 1
;;
*)
arg_num=$(( $arg_num + 1 ))
case $arg_num in
1)
first_normal_arg="$1"
shift
;;
2)
second_normal_arg="$1"
shift
;;
*)
bad_args=TRUE
esac
;;
esac
done
# Handy to have this here when adding arguments to
# see if they're working. Just edit the '0' to be '1'.
if [[ 0 == 1 ]]; then
echo "first_normal_arg: $first_normal_arg"
echo "second_normal_arg: $second_normal_arg"
echo "boolean_arg: $boolean_arg"
echo "arg_with_value: $arg_with_value"
exit 0
fi
if [[ $bad_args == TRUE || $arg_num < 2 ]]; then
echo "Usage: $(basename "$0") <first-normal-arg> <second-normal-arg> [--boolean-arg] [--arg-with-value VALUE]"
exit 1
fi