如何在 Bash 中解析命令行参数?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/192249/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I parse command line arguments in Bash?
提问by Lawrence Johnston
Say, I have a script that gets called with this line:
说,我有一个用这一行调用的脚本:
./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
or this one:
或者这个:
./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile
What's the accepted way of parsing this such that in each case (or some combination of the two) $v
, $f
, and $d
will all be set to true
and $outFile
will be equal to /fizz/someOtherFile
?
在每种情况下(或两者的某种组合)$v
,$f
, 和 $d
都将被设置为true
并且$outFile
将等于/fizz/someOtherFile
?
回答by Bruno Bronosky
Method #1: Using bash without getopt[s]
方法 #1:在没有 getopt[s] 的情况下使用 bash
Two common ways to pass key-value-pair arguments are:
传递键值对参数的两种常见方法是:
Bash Space-Separated (e.g., --option argument
) (without getopt[s])
Bash Space-Separated(例如,--option argument
)(不带 getopt[s])
Usage demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts
用法 demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts
cat >/tmp/demo-space-separated.sh <<'EOF'
#!/bin/bash
POSITIONAL=()
while [[ $# -gt 0 ]]
do
key=""
case $key in
-e|--extension)
EXTENSION=""
shift # past argument
shift # past value
;;
-s|--searchpath)
SEARCHPATH=""
shift # past argument
shift # past value
;;
-l|--lib)
LIBPATH=""
shift # past argument
shift # past value
;;
--default)
DEFAULT=YES
shift # past argument
;;
*) # unknown option
POSITIONAL+=("") # save it in an array for later
shift # past argument
;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters
echo "FILE EXTENSION = ${EXTENSION}"
echo "SEARCH PATH = ${SEARCHPATH}"
echo "LIBRARY PATH = ${LIBPATH}"
echo "DEFAULT = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n ]]; then
echo "Last line of file specified as non-opt/last argument:"
tail -1 ""
fi
EOF
chmod +x /tmp/demo-space-separated.sh
/tmp/demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts
output from copy-pasting the block above:
复制粘贴上面的块的输出:
FILE EXTENSION = conf
SEARCH PATH = /etc
LIBRARY PATH = /usr/lib
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
Bash Equals-Separated (e.g., --option=argument
) (without getopt[s])
Bash Equals-Separated(例如,--option=argument
)(没有 getopt[s])
Usage demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts
用法 demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts
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
;;
-l=*|--lib=*)
LIBPATH="${i#*=}"
shift # past argument=value
;;
--default)
DEFAULT=YES
shift # past argument with no value
;;
*)
# unknown option
;;
esac
done
echo "FILE EXTENSION = ${EXTENSION}"
echo "SEARCH PATH = ${SEARCHPATH}"
echo "LIBRARY PATH = ${LIBPATH}"
echo "DEFAULT = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n ]]; then
echo "Last line of file specified as non-opt/last argument:"
tail -1
fi
EOF
chmod +x /tmp/demo-equals-separated.sh
/tmp/demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts
output from copy-pasting the block above:
复制粘贴上面的块的输出:
FILE EXTENSION = conf
SEARCH PATH = /etc
LIBRARY PATH = /usr/lib
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
To better understand ${i#*=}
search for "Substring Removal" in this guide. It is functionally equivalent to `sed 's/[^=]*=//' <<< "$i"`
which calls a needless subprocess or `echo "$i" | sed 's/[^=]*=//'`
which calls twoneedless subprocesses.
为了更好地理解${i#*=}
在本指南中搜索“子字符串删除” 。它在功能上等同于`sed 's/[^=]*=//' <<< "$i"`
调用一个不必要的子进程或`echo "$i" | sed 's/[^=]*=//'`
调用两个不必要的子进程。
Method #2: Using bash with getopt[s]
方法 #2:使用 bash 和 getopt[s]
from: http://mywiki.wooledge.org/BashFAQ/035#getopts
来自:http: //mywiki.wooledge.org/BashFAQ/035#getopts
getopt(1) limitations(older, relatively-recent getopt
versions):
getopt(1) 限制(较旧的、相对较新的getopt
版本):
- can't handle arguments that are empty strings
- can't handle arguments with embedded whitespace
- 无法处理空字符串的参数
- 无法处理带有嵌入空格的参数
More recent getopt
versions don't have these limitations.
较新的getopt
版本没有这些限制。
Additionally, the POSIX shell (and others) offer getopts
which doesn't have these limitations. I've included a simplistic getopts
example.
此外,POSIX shell(和其他)getopts
没有这些限制。我已经包含了一个简单的getopts
例子。
Usage demo-getopts.sh -vf /etc/hosts foo bar
用法 demo-getopts.sh -vf /etc/hosts foo bar
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
output from copy-pasting the block above:
复制粘贴上面的块的输出:
verbose=1, output_file='/etc/hosts', Leftovers: foo bar
The advantages of getopts
are:
的优点getopts
是:
- It's more portable, and will work in other shells like
dash
. - It can handle multiple single options like
-vf filename
in the typical Unix way, automatically.
- 它更便携,可以在其他 shell 中工作,比如
dash
. - 它可以像
-vf filename
典型的 Unix 方式一样自动处理多个单个选项。
The disadvantage of getopts
is that it can only handle short options (-h
, not --help
) without additional code.
缺点getopts
是它只能处理短选项(-h
, not --help
)而没有额外的代码。
There is a getopts tutorialwhich explains what all of the syntax and variables mean. In bash, there is also help getopts
, which might be informative.
有一个getopts 教程解释了所有语法和变量的含义。在 bash 中,还有help getopts
,这可能会提供信息。
回答by Robert Siemer
No answer mentions enhanced getopt. And the top-voted answeris misleading:It either ignores -?vfd
style short options (requested by the OP) or options after positional arguments (also requested by the OP); and it ignores parsing-errors. Instead:
没有答案提到增强的 getopt。并且最高投票的答案具有误导性:它要么忽略-?vfd
样式短选项(由 OP 请求),要么忽略位置参数之后的选项(也由 OP 请求);它忽略解析错误。反而:
- Use enhanced
getopt
from util-linux or formerly GNU glibc.1 - It works with
getopt_long()
the C function of GNU glibc. - Has alluseful distinguishing features (the others don't have them):
- handles spaces, quoting characters and even binary in arguments2(non-enhanced
getopt
can't do this) - it can handle options at the end:
script.sh -o outFile file1 file2 -v
(getopts
doesn't do this) - allows
=
-style long options:script.sh --outfile=fileOut --infile fileIn
(allowing both is lengthy if self parsing) - allows combined short options, e.g.
-vfd
(real work if self parsing) - allows touching option-arguments, e.g.
-oOutfile
or-vfdoOutfile
- handles spaces, quoting characters and even binary in arguments2(non-enhanced
- Is so old already3that no GNU system is missing this (e.g. any Linux has it).
- You can test for its existence with:
getopt --test
→ return value 4. - Other
getopt
or shell-builtingetopts
are of limited use.
- 使用
getopt
util-linux 或以前的 GNU glibc增强。1 - 它与
getopt_long()
GNU glibc 的 C 函数一起工作。 - 具有所有有用的区别特征(其他人没有):
- 处理参数2 中的空格、引用字符甚至二进制(非增强
getopt
不能这样做) - 它可以处理在最后的选项:
script.sh -o outFile file1 file2 -v
(getopts
不这样做) - 允许
=
- 样式长选项:(script.sh --outfile=fileOut --infile fileIn
如果自解析,则允许两者都很长) - 允许组合短选项,例如
-vfd
(如果自解析的实际工作) - 允许触摸选项参数,例如
-oOutfile
或-vfdoOutfile
- 处理参数2 中的空格、引用字符甚至二进制(非增强
- 已经是3岁了,以至于没有 GNU 系统会遗漏这个(例如,任何 Linux 都有)。
- 您可以通过以下方式测试它的存在:
getopt --test
→ 返回值 4。 - 其他
getopt
或 shell-builtingetopts
的用途有限。
The following calls
以下调用
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
all return
全部返回
verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile
with the following myscript
与以下 myscript
#!/bin/bash
# saner programming env: these switches turn some bugs into errors
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
OPTIONS=dfo:v
LONGOPTS=debug,force,output:,verbose
# -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 "#!/bin/bash
while [[ "$#" -gt 0 ]]; do
case in
-d|--deploy) deploy=""; shift ;;
-u|--uglify) uglify=1 ;;
*) echo "Unknown parameter passed: "; exit 1 ;;
esac
shift
done
echo "Should deploy? $deploy"
echo "Should uglify? $uglify"
" -- "$@")
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 "" in
-d|--debug)
d=y
shift
;;
-f|--force)
f=y
shift
;;
-v|--verbose)
v=y
shift
;;
-o|--output)
outFile=""
shift 2
;;
--)
shift
break
;;
*)
echo "Programming error"
exit 3
;;
esac
done
# handle non-option arguments
if [[ $# -ne 1 ]]; then
echo "./script.sh -d dev -u
# OR:
./script.sh --deploy dev --uglify
: A single input file is required."
exit 4
fi
echo "verbose: $v, force: $f, debug: $d, in: , out: $outFile"
1enhanced getopt is available on most “bash-systems”, including Cygwin; on OS X try brew install gnu-getoptor sudo port install getopt
2the POSIX exec()
conventions have no reliable way to pass binary NULL in command line arguments; those bytes prematurely end the argument
3first version released in 1997 or before (I only tracked it back to 1997)
1增强的 getopt 可用于大多数“bash 系统”,包括 Cygwin;在 OS X 上尝试brew install gnu-getopt或sudo port install getopt
2POSIXexec()
约定没有可靠的方法在命令行参数中传递二进制 NULL;这些字节过早地结束了1997 年或之前发布的参数
3第一个版本(我只将其追溯到 1997 年)
回答by Inanc Gumus
More succinct way
更简洁的方式
script.sh
脚本文件
#!/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}
Usage:
用法:
#!/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
回答by guneysus
from : digitalpeer.comwith minor modifications
来自:digitalpeer.com稍作修改
Usage myscript.sh -p=my_prefix -s=dirname -l=libname
用法 myscript.sh -p=my_prefix -s=dirname -l=libname
bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--
To better understand ${i#*=}
search for "Substring Removal" in this guide. It is functionally equivalent to `sed 's/[^=]*=//' <<< "$i"`
which calls a needless subprocess or `echo "$i" | sed 's/[^=]*=//'`
which calls twoneedless subprocesses.
为了更好地理解${i#*=}
在本指南中搜索“子字符串删除” 。它在功能上等同于`sed 's/[^=]*=//' <<< "$i"`
调用一个不必要的子进程或`echo "$i" | sed 's/[^=]*=//'`
调用两个不必要的子进程。
回答by Matt J
getopt()
/getopts()
is a good option. Stolen from here:
getopt()
/getopts()
是一个不错的选择。从这里被盗:
The simple use of "getopt" is shown in this mini-script:
这个迷你脚本显示了“getopt”的简单用法:
while [ "$#" -gt 0 ]; do
case "" in
-n) name=""; shift 2;;
-p) pidfile=""; shift 2;;
-l) logfile=""; shift 2;;
--name=*) name="${1#*=}"; shift 1;;
--pidfile=*) pidfile="${1#*=}"; shift 1;;
--logfile=*) logfile="${1#*=}"; shift 1;;
--name|--pidfile|--logfile) echo " requires an argument" >&2; exit 1;;
-*) echo "unknown option: " >&2; exit 1;;
*) handle_argument ""; shift 1;;
esac
done
What we have said is that any of -a, -b, -c or -d will be allowed, but that -c is followed by an argument (the "c:" says that).
If we call this "g" and try it out:
我们所说的是 -a、-b、-c 或 -d 中的任何一个都将被允许,但 -c 后跟一个参数(“c:”表示)。
如果我们称之为“g”并尝试一下:
#!/usr/bin/env bash
# NOTICE: Uncomment if your script depends on bashisms.
#if [ -z "$BASH_VERSION" ]; then bash # As long as there is at least one more argument, keep looping
while [[ $# -gt 0 ]]; do
key=""
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=""
;;
# 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
$@ ; 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 "" ]; do
# Copy so we can modify it (can't modify )
OPT=""
# 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=""
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 ( ..)
eval set -- $REMAINS
echo -e "After: \n configfile='$CONFIGFILE' \n force='$FORCE' \n retry='$RETRY' \n remains='$REMAINS'"
for i ; do echo - $i ; done
We start with two arguments, and "getopt" breaks apart the options and puts each in its own argument. It also added "--".
我们从两个参数开始,“getopt”将选项分开并将每个选项放在自己的参数中。它还添加了“--”。
回答by bronson
At the risk of adding another example to ignore, here's my scheme.
冒着增加另一个例子被忽略的风险,这是我的计划。
- handles
-n arg
and--name=arg
- allows arguments at the end
- shows sane errors if anything is misspelled
- compatible, doesn't use bashisms
- readable, doesn't require maintaining state in a loop
- 把手
-n arg
和--name=arg
- 最后允许参数
- 如果有任何拼写错误,则显示正常的错误
- 兼容,不使用 bashisms
- 可读,不需要在循环中维护状态
Hope it's useful to someone.
希望它对某人有用。
./myscript --foo -b -o /fizz/file.txt
回答by Shane Day
I'm about 4 years late to this question, but want to give back. I used the earlier answers as a starting point to tidy up my old adhoc param parsing. I then refactored out the following template code. It handles both long and short params, using = or space separated arguments, as well as multiple short params grouped together. Finally it re-inserts any non-param arguments back into the $1,$2.. variables. I hope it's useful.
我对这个问题晚了大约 4 年,但想回馈。我使用较早的答案作为整理我旧的临时参数解析的起点。然后我重构了以下模板代码。它处理长参数和短参数,使用 = 或空格分隔的参数,以及组合在一起的多个短参数。最后,它将任何非参数参数重新插入 $1,$2.. 变量中。我希望它有用。
./myscript -f --bar -o=/fizz/file.txt
回答by bubla
I have found the matter to write portable parsing in scripts so frustrating that I have written Argbash- a FOSS code generator that can generate the arguments-parsing code for your script plus it has some nice features:
我发现在脚本中编写可移植解析的问题如此令人沮丧,以至于我编写了Argbash- 一个 FOSS 代码生成器,可以为您的脚本生成参数解析代码,并且它具有一些不错的功能:
回答by Ponyboy47
My answer is largely based on the answer by Bruno Bronosky, but I sort of mashed his two pure bash implementations into one that I use pretty frequently.
我的回答主要基于Bruno Bronosky 的回答,但我将他的两个纯 bash 实现混为一谈,我经常使用。
#!/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
This allows you to have both space separated options/values, as well as equal defined values.
这允许您同时拥有空格分隔的选项/值,以及相等的定义值。
So you could run your script using:
因此,您可以使用以下命令运行脚本:
./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile
OPT:v
OPT:d
OPT:o OPTARG:/fizz/someOtherFile
OPT:f
ARG:./foo/bar/someFile
as well as:
也:
##代码##and both should have the same end result.
并且两者都应该具有相同的最终结果。
PROS:
优点:
Allows for both -arg=value and -arg value
Works with any arg name that you can use in bash
- Meaning -a or -arg or --arg or -a-r-g or whatever
Pure bash. No need to learn/use getopt or getopts
允许 -arg=value 和 -arg value
适用于您可以在 bash 中使用的任何 arg 名称
- 含义 -a 或 -arg 或 --arg 或 -arg 或其他
纯粹的狂欢。无需学习/使用 getopt 或 getopts
CONS:
缺点:
Can't combine args
- Meaning no -abc. You must do -a -b -c
无法组合 args
- 意思是没有-abc。你必须做 -a -b -c
These are the only pros/cons I can think of off the top of my head
这些是我能想到的唯一优点/缺点
回答by Alek
I think this one is simple enough to use:
我认为这个很简单,可以使用:
##代码##Invocation example:
调用示例:
##代码##