假设我有一个脚本,它被使用这行调用:
./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
或者这个:
./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile
有什么接受的解析方法,可以使$v
、$f
和$d
中的每个变量(或其中某些组合)全部设置为true
,并且$outFile
等于/fizz/someOtherFile
?
假设我有一个脚本,它被使用这行调用:
./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
或者这个:
./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile
有什么接受的解析方法,可以使$v
、$f
和$d
中的每个变量(或其中某些组合)全部设置为true
,并且$outFile
等于/fizz/someOtherFile
?
--选项 参数
)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
--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/[^=]*=//'`
,它调用了两个不必要的子进程。
getopt(1) 存在以下限制(旧版本和相对近期的getopt
版本):
更新一些的 getopt
版本没有这些限制。有关更多信息,请参见这些文档。
此外,POSIX shell 和其他 shell 提供了 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
这样的 shell 中使用。-vf filename
。getopts
的缺点是它只能处理短选项(-h
而不是 --help
)而不能直接处理长选项,除非编写额外的代码。
有一个 getopts 教程 ,其中解释了所有语法和变量的含义。此外,在bash中还有 help getopts
命令,其中可能包含一些有用信息。man getopt
的输出为getopt - parse command options (enhanced)
,因此我认为这个增强版现在是标准版。 - Livvengetopts "h?vf:"
应该改为 getopts "hvf:"
,未被识别的参数会被存储为 $opt
的 ?
。引用“man builtins”中的话:“冒号和问号字符不能作为选项字符使用”。 - Simon A. Eugstergetopt
。1getopt_long()
一起使用。getopt
无法做到这一点)script.sh -o outFile file1 file2 -v
(getopts
无法做到这一点)=
风格的长选项:script.sh --outfile=fileOut --infile fileIn
(如果自解析,两者都很冗长)-vfd
(如果自解析,真的很麻烦)-oOutfile
或-vfdoOutfile
getopt --test
→ 返回值为4。getopt
或shell内置的getopts
的用途有限。myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile
myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile
myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd
myscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile
verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile
myscript
#!/bin/bash
# More safety, by turning some bugs into errors.
# Without `errexit` you don’t need ! and can replace
# ${PIPESTATUS[0]} with a simple $?, but I prefer safety.
set -o errexit -o pipefail -o noclobber -o nounset
# -allow a command to fail with !’s side effect on errexit
# -use return value from ${PIPESTATUS[0]}, because ! hosed $?
! getopt --test > /dev/null
if [[ ${PIPESTATUS[0]} -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
# -regarding ! and PIPESTATUS see above
# -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
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
# e.g. return value is 1
# then getopt has complained about wrong arguments to stdout
exit 2
fi
# 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"
1 大多数“bash系统”,包括Cygwin,都提供了增强的getopt;在OS X上,可以尝试使用brew install gnu-getopt
,brew install util-linux
或sudo port install getopt
2 POSIX的exec()
约定没有可靠的方法来传递命令行参数中的二进制NULL;这些字节会提前结束参数
3 第一个版本在1997年或之前发布(我只追溯到1997年)
getopt
。 - johncipgetopt
的唯一缺点是在包装脚本中不能方便地使用,因为当一个人在包装脚本中有少量特定于包装脚本的选项,然后将非包装脚本选项传递给被包装的可执行文件时,无法轻松实现。假设我有一个名为 mygrep
的 grep
包装器,并且我有一个 --foo
选项是 mygrep
特有的,则我不能这样做 mygrep --foo -A 2
并自动将 -A 2
传递给 grep
;我必须 这样做 mygrep --foo -- -A 2
。这是我的实现,建立在你的解决方案之上。 - Kaushal Modiman bash
。 - Robert Siemerdeploy.sh
#!/bin/bash
while [[ "$#" -gt 0 ]]; do
case $1 in
-t|--target) target="$2"; shift ;;
-u|--uglify) uglify=1 ;;
*) echo "Unknown parameter passed: $1"; exit 1 ;;
esac
shift
done
echo "Where to deploy: $target"
echo "Should uglify : $uglify"
使用方法:
./deploy.sh -t dev -u
# OR:
./deploy.sh --target dev --uglify
./script.sh -d dev -d prod
将导致 deploy == 'prod'
。尽管如此,我还是使用了它 :P :) :+1: - yairwhile (( "$#" )); do
而不是 while [[ "$#" -gt 0 ]]; do
。 - CIsForCookies从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/[^=]*=//'`
,该命令调用了 两个 不必要的子进程。
mount -t tempfs ...
。可以通过类似于以下代码来解决这个问题:while [ $# -ge 1 ]; do param=$1; shift; case $param in; -p) prefix=$1; shift;;
等等。 - Tobias Kienzler-vfd
风格的组合短选项。 - Robert Siemer--option
和 -option
,而不需要每次重复 OPTION=$i
,请使用 -*=*)
作为匹配模式和 eval ${i##*-}
。 - user8162while [ "$#" -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
*) die "unrecognized argument: $1"
或者将参数收集到一个变量中 *) args+="$1"; shift 1;;
。 - bronsonshift 2
中出现错误而导致无限循环,因为它两次调用了shift
而不是shift 2
。建议进行修改。 - lauksasgetopt()
/getopts()
是一个不错的选择。引用自这里:
这个小脚本演示了如何简单使用“getopt”:
#!/bin/bash
echo "Before getopt"
for i
do
echo $i
done
args=`getopt abc:d $*`
set -- $args
echo "After getopt"
for i
do
echo "-->$i"
done
我们所说的是,任何一个-a,-b,-c或-d都将被允许,但-c后面跟着一个参数(“c:”表示这一点)。bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--
我们从两个参数开始,"getopt"将选项分开并将每个选项放入自己的参数中。它还添加了"--"。我以早期的答案为起点,整理了我的旧adhoc参数解析。然后我重构了以下模板代码。它处理长参数和短参数,使用=或空格分隔的参数,以及多个短参数一起分组。最后,它会重新插入任何非参数参数回到$1、$2..变量中。
#!/usr/bin/env bash
# NOTICE: Uncomment if your script depends on bashisms.
#if [ -z "$BASH_VERSION" ]; then bash $0 $@ ; exit $? ; fi
echo "Before"
for i ; do echo - $i ; done
# Code template for parsing command line parameters using only portable shell
# code, while handling both long and short params, handling '-f file' and
# '-f=file' style param data and also capturing non-parameters to be inserted
# back into the shell positional parameters.
while [ -n "$1" ]; do
# Copy so we can modify it (can't modify $1)
OPT="$1"
# Detect argument termination
if [ x"$OPT" = x"--" ]; then
shift
for OPT ; do
REMAINS="$REMAINS \"$OPT\""
done
break
fi
# Parse current opt
while [ x"$OPT" != x"-" ] ; do
case "$OPT" in
# Handle --flag=value opts like this
-c=* | --config=* )
CONFIGFILE="${OPT#*=}"
shift
;;
# and --flag value opts like this
-c* | --config )
CONFIGFILE="$2"
shift
;;
-f* | --force )
FORCE=true
;;
-r* | --retry )
RETRY=true
;;
# Anything unknown is recorded for later
* )
REMAINS="$REMAINS \"$OPT\""
break
;;
esac
# Check for multiple short options
# NOTICE: be sure to update this pattern to match valid options
NEXTOPT="${OPT#-[cfr]}" # try removing single short opt
if [ x"$OPT" != x"$NEXTOPT" ] ; then
OPT="-$NEXTOPT" # multiple short opts, keep going
else
break # long form, exit inner loop
fi
done
# Done with that param. move to next
shift
done
# Set the non-parameters back into the positional parameters ($1 $2 ..)
eval set -- $REMAINS
echo -e "After: \n configfile='$CONFIGFILE' \n force='$FORCE' \n retry='$RETRY' \n remains='$REMAINS'"
for i ; do echo - $i ; done
-c1
这样带参数的选项。而使用 =
来分隔短选项和它们的参数是不寻常的... - Robert Siemer# As long as there is at least one more argument, keep looping
while [[ $# -gt 0 ]]; do
key="$1"
case "$key" in
# This is a flag type option. Will catch either -f or --foo
-f|--foo)
FOO=1
;;
# Also a flag type option. Will catch either -b or --bar
-b|--bar)
BAR=1
;;
# This is an arg value type option. Will catch -o value or --output-file value
-o|--output-file)
shift # past the key and to the value
OUTPUTFILE="$1"
;;
# This is an arg=value type option. Will catch -o=value or --output-file=value
-o=*|--output-file=*)
# No need to shift here since the value is part of the same string
OUTPUTFILE="${key#*=}"
;;
*)
# Do whatever you want with extra options
echo "Unknown option '$key'"
;;
esac
# Shift after checking all the cases to get the next option
shift
done
这使您可以同时拥有以空格分隔的选项/值和相等定义的值。
因此,您可以使用以下方式运行脚本:
./myscript --foo -b -o /fizz/file.txt
以及:
./myscript -f --bar -o=/fizz/file.txt
并且两者应该有相同的最终结果。
优点:
允许使用 -arg=value 和 -arg value 两种方式
适用于在bash中可以使用的任何参数名称
纯bash。无需学习/使用 getopt 或 getopts
缺点:
不能组合参数
shift; OUTPUTFILE="$1"
而不是OUTPUTFILE="$2"
?也许它有一个简单的答案,但我是Bash的新手。 - KasRoudra$1
作为“活动”参数出现在所有地方。 - Ponyboy47getopt
、eval
、HEREDOC
和shift
来处理带有或不带有必需值的短参数和长参数。此外,switch/case语句简洁易懂。#!/usr/bin/env bash
# usage function
function usage()
{
cat << HEREDOC
Usage: $progname [--num NUM] [--time TIME_STR] [--verbose] [--dry-run]
optional arguments:
-h, --help show this help message and exit
-n, --num NUM pass in a number
-t, --time TIME_STR pass in a time string
-v, --verbose increase the verbosity of the bash script
--dry-run do a dry run, dont change any files
HEREDOC
}
# initialize variables
progname=$(basename $0)
verbose=0
dryrun=0
num_str=
time_str=
# use getopt and store the output into $OPTS
# note the use of -o for the short options, --long for the long name options
# and a : for any option that takes a parameter
OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; usage; exit 1 ; fi
eval set -- "$OPTS"
while true; do
# uncomment the next line to see how shift is working
# echo "\$1:\"$1\" \$2:\"$2\""
case "$1" in
-h | --help ) usage; exit; ;;
-n | --num ) num_str="$2"; shift 2 ;;
-t | --time ) time_str="$2"; shift 2 ;;
--dry-run ) dryrun=1; shift ;;
-v | --verbose ) verbose=$((verbose + 1)); shift ;;
-- ) shift; break ;;
* ) break ;;
esac
done
if (( $verbose > 0 )); then
# print out all the parameters we read in
cat <<EOM
num=$num_str
time=$time_str
verbose=$verbose
dryrun=$dryrun
EOM
fi
# The rest of your script below
OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; exit 1 ; fi
eval set -- "$OPTS"
while true; do
case "$1" in
-h | --help ) usage; exit; ;;
-n | --num ) num_str="$2"; shift 2 ;;
-t | --time ) time_str="$2"; shift 2 ;;
--dry-run ) dryrun=1; shift ;;
-v | --verbose ) verbose=$((verbose + 1)); shift ;;
-- ) shift; break ;;
* ) break ;;
esac
done
简短、明了、易读且几乎覆盖所有内容(在我看来)。
希望这能帮助到某些人。
gnu-getopt
才能正常工作。请执行brew install gnu-getopt
命令进行安装。 - phyattgetopt v1.10
升级到 getopt 2.38+
。https://opensource.apple.com/source/shell_cmds/shell_cmds-216.60.1/getopt/getopt.c.auto.html - phyatt
zparseopts -D -E -M -- d=debug -debug=d
并将-d
和--debug
都放入$debug
数组中。 如果其中一个被使用,echo $+debug[1]
将返回0或1。参考:http://www.zsh.org/mla/users/2011/msg00350.html - dza=
将选项名称与选项值分离的长选项(在这两种情况下,它只是假设选项值在下一个参数中)。它还不处理短选项聚集 - 这个问题不需要它。 - Jonathan Leffler$1
,$2
等,2)使用getopts
和${OPTARG}
的标志,3)循环遍历所有参数($@
),以及4)使用$#
,$1
和shift
运算符循环遍历所有参数。 - Gabriel Staples