说,我有一个用这一行调用的脚本:
./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
或者这个:
./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile
什么是可接受的解析方式,以便在每种情况下(或两者的某种组合)$v
、$f
和 $d
都将设置为 true
并且 $outFile
将等于 {6 }?
zparseopts -D -E -M -- d=debug -debug=d
并且在 $debug
数组中同时包含 -d
和 --debug
如果使用其中一个,echo $+debug[1]
将返回 0 或 1。参考:zsh.org/mla/users/2011/msg00350.html
=
分隔选项名称和选项值的长选项(在这两种情况下,它只是假设选项值在下一个参数中)。它也不处理短选项聚类——这个问题不需要它。
$1
、$2
等,2) 带有 getopts
和 ${OPTARG}
的标志,3) 遍历所有参数($@
) 和 4) 使用 $#
、$1
和 shift
运算符循环所有参数。
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 Equals-Separated(例如,--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#*=}
,请在 this guide 中搜索“子字符串删除”。它在功能上等同于调用一个不必要的子流程的 `sed 's/[^=]*=//' <<< "$i"`
或调用 两个 不必要的子流程的 `echo "$i" | sed 's/[^=]*=//'`
。
将 bash 与 getopt[s] 一起使用
getopt(1) 限制(较旧、相对较新的 getopt
版本):
无法处理空字符串参数
无法处理带有嵌入空格的参数
较新的 getopt
版本没有这些限制。有关详细信息,请参阅这些 docs。
POSIX getopts
此外,POSIX 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 中工作。它可以以典型的 Unix 方式自动处理多个单个选项,例如 -vf 文件名。
getopts
的缺点是它只能处理短选项(-h
,而不是 --help
)而无需额外的代码。
getopts tutorial 解释了所有语法和变量的含义。在 bash 中,还有 help getopts
,它可能提供信息。
没有答案展示了增强的 getopt。并且 top-voted answer 具有误导性:它要么忽略 -vfd
样式短选项(由 OP 请求),要么忽略位置参数后的选项(也由 OP 请求);它忽略了解析错误。反而:
使用来自 util-linux 或以前的 GNU glibc.1 的增强型 getopt
它与 GNU glibc 的 C 函数 getopt_long() 一起使用。
此页面上没有其他解决方案可以做到这一切:在 arguments2 中处理空格、引用字符甚至二进制(非增强的 getopt 无法做到这一点)它可以在最后处理选项: script.sh -o outFile file1 file2 -v (getopts 不这样做)允许 =-style 长选项:script.sh --outfile=fileOut --infile fileIn(如果自解析则允许两者都很长)允许组合短选项,例如 -vfd(如果自解析则实际工作) 允许触摸选项参数,例如 -oOutfile 或 -vfdoOutfile
在 arguments2 中处理空格、引用字符甚至二进制(非增强的 getopt 不能这样做)
它可以在最后处理选项: script.sh -o outFile file1 file2 -v (getopts 不这样做)
允许 =-style 长选项: script.sh --outfile=fileOut --infile fileIn (如果自解析,则允许两者都很长)
允许组合短选项,例如 -vfd (如果自我解析,则实际工作)
允许触摸选项参数,例如 -oOutfile 或 -vfdoOutfile
已经太老了3,以至于没有任何 GNU 系统缺少它(例如,任何 Linux 都有它)。
您可以使用以下命令测试它的存在:getopt --test → return value 4。
其他 getopt 或 shell-builtin getopt 的用途有限。
以下调用
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 增强的 getopt 可用于大多数“bash 系统”,包括 Cygwin;在 OS X 上尝试 brew install gnu-getopt 或 sudo port install getopt
2 POSIX exec()
约定没有可靠的方法在命令行参数中传递二进制 NULL;这些字节过早地结束了论点
3 1997 年或更早发布的第一个版本(我只追踪到 1997 年)
getopt
是可行的方法。
getopt
的唯一警告是,它不能方便地在包装脚本中使用,其中一个可能只有几个特定于包装脚本的选项,然后将非包装脚本选项传递给包装的可执行文件,完好无损。假设我有一个名为 mygrep
的 grep
包装器,并且我有一个特定于 mygrep
的选项 --foo
,那么我不能执行 mygrep --foo -A 2
,并让 -A 2
自动传递给 grep
;我需要做mygrep --foo -- -A 2
。 这里是您解决方案之上的my implementation。
部署.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 --debug dev --uglify fast --verbose
结束行,则必须 while [[ "$#" > 1 ]]
。示例:gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58
./script.sh -d dev -d prod
将导致 deploy == 'prod'
。反正我用过:P :) :+1:
while (( "$#" )); do
而不是 while [[ "$#" -gt 0 ]]; do
从 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#*=}
,请在 this guide 中搜索“子字符串删除”。它在功能上等同于调用一个不必要的子流程的 `sed 's/[^=]*=//' <<< "$i"`
或调用 两个 不必要的子流程的 `echo "$i" | sed 's/[^=]*=//'`
。
mount -t tempfs ...
。可以通过 while [ $# -ge 1 ]; do param=$1; shift; case $param in; -p) prefix=$1; shift;;
等解决此问题
-vfd
样式组合的短选项。
--option
和 -option
而不是每次都重复 OPTION=$i
,请使用 -*=*)
作为匹配模式和 eval ${i##*-}
。
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
最后允许参数
如果有任何拼写错误,则显示正常错误
兼容,不使用 bashisms
可读,不需要在循环中维护状态
*) die "unrecognized argument: $1"
或将 args 收集到变量 *) args+="$1"; shift 1;;
中。
shift 2
上的错误导致无限循环,发出 shift
两次而不是 shift 2
。建议编辑。
getopt
或 getopts
。
getopt()
/getopts()
是一个不错的选择。从 here 复制:
这个小脚本显示了“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:”表示)。如果我们称其为“g”并尝试一下:
bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--
我们从两个参数开始,“getopt”分解选项并将每个选项放在自己的参数中。它还添加了“--”。
我发现在脚本中编写可移植解析非常令人沮丧,以至于我编写了 Argbash - 一个 FOSS 代码生成器,可以为您的脚本生成参数解析代码,而且它具有一些不错的功能:
我以较早的答案为起点来整理我旧的临时参数解析。然后我重构了以下模板代码。它处理长参数和短参数,使用 = 或空格分隔的参数,以及组合在一起的多个短参数。最后,它将所有非参数参数重新插入到 $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
。并且使用 =
将短选项与其论点分开是不寻常的......
# 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 中使用的任何 arg 名称含义 -a 或 -arg 或 --arg 或 -arg 或其他
含义 -a 或 -arg 或 --arg 或 -arg 或其他
纯粹的狂欢。无需学习/使用 getopt 或 getopts
缺点:
不能组合 args 意味着没有 -abc。你必须做 -a -b -c
意思是没有-abc。你必须做 -a -b -c
shift; OUTPUTFILE="$1"
而不是 OUTPUTFILE="$2"
?也许它有一个简单的答案,但我是 bash 的新手
$1
作为“活动”参数
此示例说明如何使用 getopt
和 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
简短、中肯、易读,并且几乎可以处理所有事情(恕我直言)。
希望对某人有所帮助。
扩展@bruno-bronosky 的答案,我添加了一个“预处理器”来处理一些常见的格式:
将 --longopt=val 扩展为 --longopt val
将 -xyz 扩展为 -x -y -z
支持 -- 表示标志的结束
显示意外选项的错误
紧凑且易于阅读的选项开关
#!/bin/bash
# Report usage
usage() {
echo "Usage:"
echo "$(basename "$0") [options] [--] [file1, ...]"
}
invalid() {
echo "ERROR: Unrecognized argument: $1" >&2
usage
exit 1
}
# Pre-process options to:
# - expand -xyz into -x -y -z
# - expand --longopt=arg into --longopt arg
ARGV=()
END_OF_OPT=
while [[ $# -gt 0 ]]; do
arg="$1"; shift
case "${END_OF_OPT}${arg}" in
--) ARGV+=("$arg"); END_OF_OPT=1 ;;
--*=*)ARGV+=("${arg%%=*}" "${arg#*=}") ;;
--*) ARGV+=("$arg") ;;
-*) for i in $(seq 2 ${#arg}); do ARGV+=("-${arg:i-1:1}"); done ;;
*) ARGV+=("$arg") ;;
esac
done
# Apply pre-processed options
set -- "${ARGV[@]}"
# Parse options
END_OF_OPT=
POSITIONAL=()
while [[ $# -gt 0 ]]; do
case "${END_OF_OPT}${1}" in
-h|--help) usage; exit 0 ;;
-p|--password) shift; PASSWORD="$1" ;;
-u|--username) shift; USERNAME="$1" ;;
-n|--name) shift; names+=("$1") ;;
-q|--quiet) QUIET=1 ;;
-C|--copy) COPY=1 ;;
-N|--notify) NOTIFY=1 ;;
--stdin) READ_STDIN=1 ;;
--) END_OF_OPT=1 ;;
-*) invalid "$1" ;;
*) POSITIONAL+=("$1") ;;
esac
shift
done
# Restore positional parameters
set -- "${POSITIONAL[@]}"
END_OF_OPT=1
是否真的需要在此行:--*) ARGV+=("$arg"); END_OF_OPT=1 ;;
。如果留在其中,如果它包含在 --quiet
之后(或任何其他长样式布尔选项),它将无法解析 --username=fred
。例如,script.sh --quiet --username=fred
以 Unrecognized argument: --username=fred
失败(尽管 script.sh --quiet --username fred
工作正常)。我在我的脚本中取出了那个 END_OF_OPT=1
,现在它可以工作了,但不确定这是否会破坏我不知道的其他情况。
如果您正在制作可与其他实用程序互换的脚本,那么以下灵活性可能会很有用。
任何一个:
command -x=myfilename.ext --another_switch
或者:
command -x myfilename.ext --another_switch
这是代码:
STD_IN=0
prefix=""
key=""
value=""
for keyValue in "$@"
do
case "${prefix}${keyValue}" in
-i=*|--input_filename=*) key="-i"; value="${keyValue#*=}";;
-ss=*|--seek_from=*) key="-ss"; value="${keyValue#*=}";;
-t=*|--play_seconds=*) key="-t"; value="${keyValue#*=}";;
-|--stdin) key="-"; value=1;;
*) value=$keyValue;;
esac
case $key in
-i) MOVIE=$(resolveMovie "${value}"); prefix=""; key="";;
-ss) SEEK_FROM="${value}"; prefix=""; key="";;
-t) PLAY_SECONDS="${value}"; prefix=""; key="";;
-) STD_IN=${value}; prefix=""; key="";;
*) prefix="${keyValue}=";;
esac
done
我认为这个使用起来很简单:
#!/bin/bash
#
readopt='getopts $opts opt;rc=$?;[ "$rc$opt" = "0?" ]&&exit 1;[ $rc = 0 ]||{ shift $[OPTIND-1];false; }'
opts=vfdo:
# Enumerating options
while eval "$readopt"
do
echo OPT:$opt ${OPTARG+OPTARG:$OPTARG}
done
# Enumerating arguments
for arg
do
echo ARG:$arg
done
调用示例:
./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile
OPT:v
OPT:d
OPT:o OPTARG:/fizz/someOtherFile
OPT:f
ARG:./foo/bar/someFile
-a=1
作为 argc 样式。我更喜欢首先放置主要选项 -options,然后是具有单间距 -o option
的特殊选项。我正在寻找阅读 argvs 的最简单与更好的方法。
./myscript -v -d fail -o /fizz/someOtherFile -f ./foo/bar/someFile
。 -d 选项未设置为 d:
我给你函数 parse_params
,它将从命令行解析参数。
这是一个纯 Bash 解决方案,没有额外的实用程序。不污染全局范围。毫不费力地为您返回简单易用的变量,您可以在其上构建进一步的逻辑。参数前的破折号数量无关紧要(--all 等于 -all 等于 all=all)
下面的脚本是一个复制粘贴工作演示。请参阅 show_use
函数以了解如何使用 parse_params
。
限制:
不支持空格分隔的参数 (-d 1) 参数名称将丢失破折号,因此 --any-param 和 -anyparam 是等效的 eval $(parse_params "$@") 必须在 bash 函数中使用(它在全局中不起作用范围)
#!/bin/bash
# Universal Bash parameter parsing
# Parse equal sign separated params into named local variables
# Standalone named parameter value will equal its param name (--force creates variable $force=="force")
# Parses multi-valued named params into an array (--path=path1 --path=path2 creates ${path[*]} array)
# Puts un-named params as-is into ${ARGV[*]} array
# Additionally puts all named params as-is into ${ARGN[*]} array
# Additionally puts all standalone "option" params as-is into ${ARGO[*]} array
# @author Oleksii Chekulaiev
# @version v1.4.1 (Jul-27-2018)
parse_params ()
{
local existing_named
local ARGV=() # un-named params
local ARGN=() # named params
local ARGO=() # options (--params)
echo "local ARGV=(); local ARGN=(); local ARGO=();"
while [[ "$1" != "" ]]; do
# Escape asterisk to prevent bash asterisk expansion, and quotes to prevent string breakage
_escaped=${1/\*/\'\"*\"\'}
_escaped=${_escaped//\'/\\\'}
_escaped=${_escaped//\"/\\\"}
# If equals delimited named parameter
nonspace="[^[:space:]]"
if [[ "$1" =~ ^${nonspace}${nonspace}*=..* ]]; then
# Add to named parameters array
echo "ARGN+=('$_escaped');"
# key is part before first =
local _key=$(echo "$1" | cut -d = -f 1)
# Just add as non-named when key is empty or contains space
if [[ "$_key" == "" || "$_key" =~ " " ]]; then
echo "ARGV+=('$_escaped');"
shift
continue
fi
# val is everything after key and = (protect from param==value error)
local _val="${1/$_key=}"
# remove dashes from key name
_key=${_key//\-}
# skip when key is empty
# search for existing parameter name
if (echo "$existing_named" | grep "\b$_key\b" >/dev/null); then
# if name already exists then it's a multi-value named parameter
# re-declare it as an array if needed
if ! (declare -p _key 2> /dev/null | grep -q 'declare \-a'); then
echo "$_key=(\"\$$_key\");"
fi
# append new value
echo "$_key+=('$_val');"
else
# single-value named parameter
echo "local $_key='$_val';"
existing_named=" $_key"
fi
# If standalone named parameter
elif [[ "$1" =~ ^\-${nonspace}+ ]]; then
# remove dashes
local _key=${1//\-}
# Just add as non-named when key is empty or contains space
if [[ "$_key" == "" || "$_key" =~ " " ]]; then
echo "ARGV+=('$_escaped');"
shift
continue
fi
# Add to options array
echo "ARGO+=('$_escaped');"
echo "local $_key=\"$_key\";"
# non-named parameter
else
# Escape asterisk to prevent bash asterisk expansion
_escaped=${1/\*/\'\"*\"\'}
echo "ARGV+=('$_escaped');"
fi
shift
done
}
#--------------------------- DEMO OF THE USAGE -------------------------------
show_use ()
{
eval $(parse_params "$@")
# --
echo "${ARGV[0]}" # print first unnamed param
echo "${ARGV[1]}" # print second unnamed param
echo "${ARGN[0]}" # print first named param
echo "${ARG0[0]}" # print first option param (--force)
echo "$anyparam" # print --anyparam value
echo "$k" # print k=5 value
echo "${multivalue[0]}" # print first value of multi-value
echo "${multivalue[1]}" # print second value of multi-value
[[ "$force" == "force" ]] && echo "\$force is set so let the force be with you"
}
show_use "param 1" --anyparam="my value" param2 k=5 --force --multi-value=test1 --multi-value=test2
show_use "$@"
如果 #1 已安装 getopts 并且 #2 您打算在同一平台上运行它,则 getopts 效果很好。 OSX 和 Linux(例如)在这方面表现不同。
这是一个支持等于、非等于和布尔标志的(非 getopts)解决方案。例如,您可以以这种方式运行您的脚本:
./script --arg1=value1 --arg2 value2 --shouldClean
# parse the arguments.
COUNTER=0
ARGS=("$@")
while [ $COUNTER -lt $# ]
do
arg=${ARGS[$COUNTER]}
let COUNTER=COUNTER+1
nextArg=${ARGS[$COUNTER]}
if [[ $skipNext -eq 1 ]]; then
echo "Skipping"
skipNext=0
continue
fi
argKey=""
argVal=""
if [[ "$arg" =~ ^\- ]]; then
# if the format is: -key=value
if [[ "$arg" =~ \= ]]; then
argVal=$(echo "$arg" | cut -d'=' -f2)
argKey=$(echo "$arg" | cut -d'=' -f1)
skipNext=0
# if the format is: -key value
elif [[ ! "$nextArg" =~ ^\- ]]; then
argKey="$arg"
argVal="$nextArg"
skipNext=1
# if the format is: -key (a boolean flag)
elif [[ "$nextArg" =~ ^\- ]] || [[ -z "$nextArg" ]]; then
argKey="$arg"
argVal=""
skipNext=0
fi
# if the format has not flag, just a value.
else
argKey=""
argVal="$arg"
skipNext=0
fi
case "$argKey" in
--source-scmurl)
SOURCE_URL="$argVal"
;;
--dest-scmurl)
DEST_URL="$argVal"
;;
--version-num)
VERSION_NUM="$argVal"
;;
-c|--clean)
CLEAN_BEFORE_START="1"
;;
-h|--help|-help|--h)
showUsage
exit
;;
esac
done
另一个选项解析器(生成器)
一个优雅的 shell 脚本选项解析器(完全支持所有 POSIX shell)https://github.com/ko1nksm/getoptions(更新:2021-05-02 发布的 v3.3.0)
getoptions 是一个用 POSIX 兼容的 shell 脚本编写的新选项解析器(生成器),并于 2020 年 8 月发布。它适用于那些希望在 shell 脚本中支持 POSIX / GNU 样式选项语法的人。
支持的语法有 -a
、+a
、-abc
、-vvv
、-p VALUE
、-pVALUE
、--flag
、--no-flag
、--with-flag
、--without-flag
、--param VALUE
、{12 },--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
}
另一个 Shell 参数解析器(ASAP)
POSIX 兼容,没有 getopt(s)
我受到相对简单的 answer by @bronson 的启发,并试图尝试改进它(不增加太多复杂性)。结果如下:
使用任何 -n [arg]、-abn [arg]、--name [arg] 和 --name=arg 样式的选项;
参数可以以任何顺序出现,循环后只有位置参数留在 $@ 中;
使用 -- 强制将剩余的参数视为位置参数;
检测无效选项和缺失参数;
不依赖于 getopt(s) 或外部工具(一个功能使用简单的 sed 命令);
便携,紧凑,可读性强,具有独立功能。
# Convenience functions.
usage_error () { echo >&2 "$(basename $0): $1"; exit 2; }
assert_argument () { test "$1" != "$EOL" || usage_error "$2 requires an argument"; }
# One loop, nothing more.
if [ "$#" != 0 ]; then
EOL=$(printf '\1\3\3\7')
set -- "$@" "$EOL"
while [ "$1" != "$EOL" ]; do
opt="$1"; shift
case "$opt" in
# Your options go here.
-f|--flag) flag='true';;
-n|--name) assert_argument "$1" "$opt"; name="$1"; shift;;
# Arguments processing. You may remove any unneeded line after the 1st.
-|''|[!-]*) set -- "$@" "$opt";; # positional argument, rotate to the end
--*=*) set -- "${opt%%=*}" "${opt#*=}" "$@";; # convert '--name=arg' to '--name' 'arg'
-[!-]?*) set -- $(echo "${opt#-}" | sed 's/\(.\)/ -\1/g') "$@";; # convert '-abc' to '-a' '-b' '-c'
--) while [ "$1" != "$EOL" ]; do set -- "$@" "$1"; shift; done;; # process remaining arguments as positional
-*) usage_error "unknown option: '$opt'";; # catch misspelled options
*) usage_error "this should NEVER happen ($opt)";; # sanity test for previous patterns
esac
done
shift # $EOL
fi
# Do something cool with "$@"... \o/
注意: 我知道... 二进制模式 0x01030307
的参数可能会破坏逻辑。但是,如果有人在命令行中传递这样的参数,他们应该得到它。
$EOL
不是二进制序列。它只是一个长度为 12 的可打印字符串(包含四个反斜杠)。您的意思是使用 printf
而不是 echo
?我不知道这是否会影响 POSIX 合规性。
printf
个 是 ...
我想提交我的项目:https://github.com/flyingangel/argparser
source argparser.sh
parse_args "$@"
就那么简单。环境将填充与参数同名的变量
这就是我在函数中所做的,以避免破坏 getopts 在堆栈中更高位置同时运行:
function waitForWeb () {
local OPTIND=1 OPTARG OPTION
local host=localhost port=8080 proto=http
while getopts "h:p:r:" OPTION; do
case "$OPTION" in
h)
host="$OPTARG"
;;
p)
port="$OPTARG"
;;
r)
proto="$OPTARG"
;;
esac
done
...
}
我想提供我的选项解析版本,它允许以下内容:
-s p1
--stage p1
-w somefolder
--workfolder somefolder
-sw p1 somefolder
-e=hello
也允许这样做(可能是不需要的):
-s--workfolder p1 somefolder
-se=hello p1
-swe=hello p1 somefolder
您必须在使用前决定是否将 = 用于选项。这是为了保持代码干净(ish)。
while [[ $# > 0 ]]
do
key="$1"
while [[ ${key+x} ]]
do
case $key in
-s*|--stage)
STAGE="$2"
shift # option has parameter
;;
-w*|--workfolder)
workfolder="$2"
shift # option has parameter
;;
-e=*)
EXAMPLE="${key#*=}"
break # option has been fully handled
;;
*)
# unknown option
echo Unknown option: $key #1>&2
exit 10 # either this: my preferred way to handle unknown options
break # or this: do this to signal the option has been handled (if exit isn't used)
;;
esac
# prepare for next option in this key, if any
[[ "$key" = -? || "$key" == --* ]] && unset key || key="${key/#-?/-}"
done
shift # option(s) fully processed, proceed to next input argument
done
有几种方法可以解析 cmdline args(例如 GNU getopt(不可移植)vs BSD(MacOS)getopt vs getopts) - 都有问题。这个解决方案
是便携的!
零依赖,仅依赖 bash 内置
允许短期和长期选择
处理空格或同时在选项和参数之间使用 = 分隔符
支持串联短选项样式 -vxf
处理带有可选参数的选项(例如 --color 与 --color=always),
正确检测并报告未知选项
支持 -- 表示选项结束,以及
与相同功能集的替代方案相比,不需要代码膨胀。即简洁,因此更易于维护
示例:任何
# flag
-f
--foo
# option with required argument
-b"Hello World"
-b "Hello World"
--bar "Hello World"
--bar="Hello World"
# option with optional argument
--baz
--baz="Optional Hello"
#!/usr/bin/env bash
usage() {
cat - >&2 <<EOF
NAME
program-name.sh - Brief description
SYNOPSIS
program-name.sh [-h|--help]
program-name.sh [-f|--foo]
[-b|--bar <arg>]
[--baz[=<arg>]]
[--]
FILE ...
REQUIRED ARGUMENTS
FILE ...
input files
OPTIONS
-h, --help
Prints this and exits
-f, --foo
A flag option
-b, --bar <arg>
Option requiring an argument <arg>
--baz[=<arg>]
Option that has an optional argument <arg>. If <arg>
is not specified, defaults to 'DEFAULT'
--
Specify end of options; useful if the first non option
argument starts with a hyphen
EOF
}
fatal() {
for i; do
echo -e "${i}" >&2
done
exit 1
}
# For long option processing
next_arg() {
if [[ $OPTARG == *=* ]]; then
# for cases like '--opt=arg'
OPTARG="${OPTARG#*=}"
else
# for cases like '--opt arg'
OPTARG="${args[$OPTIND]}"
OPTIND=$((OPTIND + 1))
fi
}
# ':' means preceding option character expects one argument, except
# first ':' which make getopts run in silent mode. We handle errors with
# wildcard case catch. Long options are considered as the '-' character
optspec=":hfb:-:"
args=("" "$@") # dummy first element so $1 and $args[1] are aligned
while getopts "$optspec" optchar; do
case "$optchar" in
h) usage; exit 0 ;;
f) foo=1 ;;
b) bar="$OPTARG" ;;
-) # long option processing
case "$OPTARG" in
help)
usage; exit 0 ;;
foo)
foo=1 ;;
bar|bar=*) next_arg
bar="$OPTARG" ;;
baz)
baz=DEFAULT ;;
baz=*) next_arg
baz="$OPTARG" ;;
-) break ;;
*) fatal "Unknown option '--${OPTARG}'" "see '${0} --help' for usage" ;;
esac
;;
*) fatal "Unknown option: '-${OPTARG}'" "See '${0} --help' for usage" ;;
esac
done
shift $((OPTIND-1))
if [ "$#" -eq 0 ]; then
fatal "Expected at least one required argument FILE" \
"See '${0} --help' for usage"
fi
echo "foo=$foo, bar=$bar, baz=$baz, files=${@}"
保留未处理参数的解决方案。包括演示。
这是我的解决方案。它非常灵活,不像其他人,不应该需要外部包并干净地处理剩余的参数。
用法是:./myscript -flag flagvariable -otherflag flagvar2
您所要做的就是编辑validflags 行。它在前面加上一个连字符并搜索所有参数。然后它将下一个参数定义为标志名称,例如
./myscript -flag flagvariable -otherflag flagvar2
echo $flag $otherflag
flagvariable flagvar2
主要代码(简短版本,详细示例,下面还有一个错误版本):
#!/usr/bin/env bash
#shebang.io
validflags="rate time number"
count=1
for arg in $@
do
match=0
argval=$1
for flag in $validflags
do
sflag="-"$flag
if [ "$argval" == "$sflag" ]
then
declare $flag=$2
match=1
fi
done
if [ "$match" == "1" ]
then
shift 2
else
leftovers=$(echo $leftovers $argval)
shift
fi
count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers
带有内置 echo 演示的详细版本:
#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
echo "all args
$@"
validflags="rate time number"
count=1
for arg in $@
do
match=0
argval=$1
# argval=$(echo $@ | cut -d ' ' -f$count)
for flag in $validflags
do
sflag="-"$flag
if [ "$argval" == "$sflag" ]
then
declare $flag=$2
match=1
fi
done
if [ "$match" == "1" ]
then
shift 2
else
leftovers=$(echo $leftovers $argval)
shift
fi
count=$(($count+1))
done
#Cleanup then restore the leftovers
echo "pre final clear args:
$@"
shift $#
echo "post final clear args:
$@"
set -- $leftovers
echo "all post set args:
$@"
echo arg1: $1 arg2: $2
echo leftovers: $leftovers
echo rate $rate time $time number $number
最后一个,如果传递了一个无效的参数,这个错误就会出错。
#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
validflags="rate time number"
count=1
for arg in $@
do
argval=$1
match=0
if [ "${argval:0:1}" == "-" ]
then
for flag in $validflags
do
sflag="-"$flag
if [ "$argval" == "$sflag" ]
then
declare $flag=$2
match=1
fi
done
if [ "$match" == "0" ]
then
echo "Bad argument: $argval"
exit 1
fi
shift 2
else
leftovers=$(echo $leftovers $argval)
shift
fi
count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers
echo rate $rate time $time number $number
echo leftovers: $leftovers
优点:它做了什么,它处理得很好。它保留了许多其他解决方案没有的未使用的参数。它还允许调用变量而无需在脚本中手动定义。如果没有给出相应的参数,它还允许预填充变量。 (参见详细示例)。
缺点:无法解析单个复杂的 arg 字符串,例如 -xcvf 将作为单个参数处理。不过,您可以稍微轻松地将其他代码写入我的代码中以添加此功能。
请注意,getopt(1)
是 AT&T 的一个短暂错误。
getopt 是在 1984 年创建的,但在 1986 年已经被埋没了,因为它并不是真正可用的。
getopt
非常过时的一个证据是 getopt(1)
手册页仍然提到 "$*"
而不是 "$@"
,它是在 1986 年与内置的 getopts(1)
shell 按顺序添加到 Bourne Shell 中的处理内部有空格的参数。
顺便说一句:如果您对解析 shell 脚本中的长选项感兴趣,可能有兴趣知道 libc (Solaris) 的 getopt(3)
实现和 ksh93
都添加了一个统一的长选项实现,该实现支持长选项作为别名短选项。这会导致 ksh93
和 Bourne Shell
通过 getopts
为长选项实现统一接口。
从 Bourne Shell 手册页中获取的长选项示例:
getopts "f:(file)(input-file)o:(output-file)" 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
基于此处的其他答案,这是我的版本:
#!/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!
混合位置和基于标志的参数
--param=arg(等于分隔)
在位置参数之间自由混合标志:
./script.sh dumbo 127.0.0.1 --environment=production -q -d
./script.sh dumbo --environment=production 127.0.0.1 --quiet -d
可以通过一种相当简洁的方法来完成:
# process flags
pointer=1
while [[ $pointer -le $# ]]; do
param=${!pointer}
if [[ $param != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
else
case $param in
# paramter-flags with arguments
-e=*|--environment=*) environment="${param#*=}";;
--another=*) another="${param#*=}";;
# binary flags
-q|--quiet) quiet=true;;
-d) debug=true;;
esac
# splice out pointer frame from positional list
[[ $pointer -gt 1 ]] \
&& set -- ${@:1:((pointer - 1))} ${@:((pointer + 1)):$#} \
|| set -- ${@:((pointer + 1)):$#};
fi
done
# positional remain
node_name=$1
ip_address=$2
--param arg(空格分隔)
不混合 --flag=value
和 --flag value
样式通常更清楚。
./script.sh dumbo 127.0.0.1 --environment production -q -d
这读起来有点冒险,但仍然有效
./script.sh dumbo --environment production 127.0.0.1 --quiet -d
资源
# process flags
pointer=1
while [[ $pointer -le $# ]]; do
if [[ ${!pointer} != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
else
param=${!pointer}
((pointer_plus = pointer + 1))
slice_len=1
case $param in
# paramter-flags with arguments
-e|--environment) environment=${!pointer_plus}; ((slice_len++));;
--another) another=${!pointer_plus}; ((slice_len++));;
# binary flags
-q|--quiet) quiet=true;;
-d) debug=true;;
esac
# splice out pointer frame from positional list
[[ $pointer -gt 1 ]] \
&& set -- ${@:1:((pointer - 1))} ${@:((pointer + $slice_len)):$#} \
|| set -- ${@:((pointer + $slice_len)):$#};
fi
done
# positional remain
node_name=$1
ip_address=$2
我编写了一个 bash 助手来编写一个不错的 bash 工具
项目主页:https://gitlab.mbedsys.org/mbedsys/bashopts
例子:
#!/bin/bash -ei
# load the library
. bashopts.sh
# Enable backtrace dusplay on error
trap 'bashopts_exit_handle' ERR
# Initialize the library
bashopts_setup -n "$0" -d "This is myapp tool description displayed on help message" -s "$HOME/.config/myapprc"
# Declare the options
bashopts_declare -n first_name -l first -o f -d "First name" -t string -i -s -r
bashopts_declare -n last_name -l last -o l -d "Last name" -t string -i -s -r
bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\$first_name \$last_name"
bashopts_declare -n age -l number -d "Age" -t number
bashopts_declare -n email_list -t string -m add -l email -d "Email adress"
# Parse arguments
bashopts_parse_args "$@"
# Process argument
bashopts_process_args
会提供帮助:
NAME:
./example.sh - This is myapp tool description displayed on help message
USAGE:
[options and commands] [-- [extra args]]
OPTIONS:
-h,--help Display this help
-n,--non-interactive true Non interactive mode - [$bashopts_non_interactive] (type:boolean, default:false)
-f,--first "John" First name - [$first_name] (type:string, default:"")
-l,--last "Smith" Last name - [$last_name] (type:string, default:"")
--display-name "John Smith" Display name - [$display_name] (type:string, default:"$first_name $last_name")
--number 0 Age - [$age] (type:number, default:0)
--email Email adress - [$email_list] (type:string, default:"")
请享用 :)
这是我的方法 - 使用正则表达式。
没有getopts
它处理短参数块 -qwerty
它处理短参数 -q -w -e
它处理长选项--qwerty
您可以将属性传递给短选项或长选项(如果您使用的是短选项块,则属性附加到最后一个选项)
您可以使用空格或 = 来提供属性,但属性匹配直到遇到连字符+空格“分隔符”,所以在 --q=qwe ty qwe ty 是一个属性
它处理上述所有内容的混合,因此 -oa -op attr ibute --option=att ribute --op-tion 属性 --option att-ribute 是有效的
脚本:
#!/usr/bin/env sh
help_menu() {
echo "Usage:
${0##*/} [-h][-l FILENAME][-d]
Options:
-h, --help
display this help and exit
-l, --logfile=FILENAME
filename
-d, --debug
enable debug
"
}
parse_options() {
case $opt in
h|help)
help_menu
exit
;;
l|logfile)
logfile=${attr}
;;
d|debug)
debug=true
;;
*)
echo "Unknown option: ${opt}\nRun ${0##*/} -h for help.">&2
exit 1
esac
}
options=$@
until [ "$options" = "" ]; do
if [[ $options =~ (^ *(--([a-zA-Z0-9-]+)|-([a-zA-Z0-9-]+))(( |=)(([\_\.\?\/\\a-zA-Z0-9]?[ -]?[\_\.\?a-zA-Z0-9]+)+))?(.*)|(.+)) ]]; then
if [[ ${BASH_REMATCH[3]} ]]; then # for --option[=][attribute] or --option[=][attribute]
opt=${BASH_REMATCH[3]}
attr=${BASH_REMATCH[7]}
options=${BASH_REMATCH[9]}
elif [[ ${BASH_REMATCH[4]} ]]; then # for block options -qwert[=][attribute] or single short option -a[=][attribute]
pile=${BASH_REMATCH[4]}
while (( ${#pile} > 1 )); do
opt=${pile:0:1}
attr=""
pile=${pile/${pile:0:1}/}
parse_options
done
opt=$pile
attr=${BASH_REMATCH[7]}
options=${BASH_REMATCH[9]}
else # leftovers that don't match
opt=${BASH_REMATCH[10]}
options=""
fi
parse_options
fi
done
假设我们创建了一个名为 test_args.sh
的 shell 脚本,如下所示
#!/bin/sh
until [ $# -eq 0 ]
do
name=${1:1}; shift;
if [[ -z "$1" || $1 == -* ]] ; then eval "export $name=true"; else eval "export $name=$1"; shift; fi
done
echo "year=$year month=$month day=$day flag=$flag"
在我们运行以下命令后:
sh test_args.sh -year 2017 -flag -month 12 -day 22
输出将是:
year=2017 month=12 day=22 flag=true
我想分享我为解析选项所做的事情。这里的答案没有满足我的一些需求,所以我不得不想出这个:https://github.com/MihirLuthra/bash_option_parser
这支持:
子选项解析
选项的别名
可选参数
可变参数
打印使用和错误
假设我们有一个名为 fruit
的命令,用法如下:
fruit <fruit-name> ...
[-e|—-eat|—-chew]
[-c|--cut <how> <why>]
<command> [<args>]
-e
不带参数
-c
带两个参数,即如何剪切以及为什么要剪切
fruit
本身至少需要一个参数。
<command>
用于像 apple
这样的子选项, orange
等(类似于具有子选项 commit
、push
等的 git
)
所以要解析它:
parse_options \
'fruit' '1 ...' \
'-e' , '--eat' , '--chew' '0' \
'-c' , '--cut' '1 1' \
'apple' 'S' \
'orange' 'S' \
';' \
"$@"
现在,如果有任何使用错误,可以使用 option_parser_error_msg
打印如下:
retval=$?
if [ $retval -ne 0 ]; then
# this will manage error messages if
# insufficient or extra args are supplied
option_parser_error_msg "$retval"
# This will print the usage
print_usage 'fruit'
exit 1
fi
现在检查是否通过了某些选项,
if [ -n "${OPTIONS[-c]}" ]
then
echo "-c was passed"
# args can be accessed in a 2D-array-like format
echo "Arg1 to -c = ${ARGS[-c,0]}"
echo "Arg2 to -c = ${ARGS[-c,1]}"
fi
子选项解析也可以通过将 $shift_count
传递给 parse_options_detailed
来完成,这使得它在将 args 移动到子选项的 args 后开始解析。在此 example 中进行了演示。
repository 中的自述文件和示例中提供了详细说明。
使用来自 bash-modules 的模块“参数”
例子:
#!/bin/bash
. import.sh log arguments
NAME="world"
parse_arguments "-n|--name)NAME;S" -- "$@" || {
error "Cannot parse command line."
exit 1
}
info "Hello, $NAME!"
getopt
,它包括getopts
的所有功能,然后是一些功能。 Ubuntu 13.04 上的man getopt
输出getopt - parse command options (enhanced)
作为名称,所以我认为这个增强版本现在是标准的。getopt
不是 GNU 实用程序,它是util-linux
的一部分。-gt 0
,请在esac
之后删除您的shift
,将所有shift
增加 1 并添加这种情况:*) break;;
您可以处理非可选参数。例如:pastebin.com/6DJ57HTcgetopts "h?vf:"
应该是不带问号的getopts "hvf:"
。无法识别的参数在$opt
中存储为?
。引自man builtins
:“The colon and question mark characters may not be used as option characters.”