如何创建一个带参数的 bash 脚本?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4587076/
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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 20:01:45  来源:igfitidea点击:

How to create a bash script that takes arguments?

bash

提问by A Question Asker

I already know about getopts, and this is fine, but it is annoying that you have to have a flag even for mandatory arguments.

我已经了解了 getopts,这很好,但令人讨厌的是,即使是强制性参数也必须有一个标志。

Ideally, I'd like to be able to have a script which receives arguments in this form:

理想情况下,我希望能够有一个以这种形式接收参数的脚本:

script.sh [optional arguments] [anything required]

for example

例如

script.sh -rvx output_file.txt

where the script says you HAVE to have an output file. Is there any easy way to do this?

脚本说你必须有一个输出文件。有什么简单的方法可以做到这一点吗?

As far as I know, with getopts it would have to look like: script.sh -rvx -f output_file.txt, and that is just not very clean.

据我所知,使用 getopts 它必须看起来像: script.sh -rvx -f output_file.txt,这不是很干净。

I can also use python if necessary, but only have 2.4 available, which is a bit dated.

如有必要,我也可以使用 python,但只有 2.4 可用,这有点过时了。

回答by sorpigal

Don't use the getopts builtin, use getopt(1) instead. They are (subtly) different and do different things well. For you scenario you could do this:

不要使用内置的 getopts,而是使用 getopt(1)。他们(微妙地)不同,并且擅长做不同的事情。对于你的场景,你可以这样做:

#!/bin/bash

eval set -- $(getopt -n 
shift $(( ${OPTIND} - 1 )); echo "${*}"
-o "-rvxl:" -- "$@") declare r v x l declare -a files while [ $# -gt 0 ] ; do case "" in -r) r=1 ; shift ;; -v) v=1 ; shift ;; -x) x=1 ; shift ;; -l) shift ; l="" ; shift ;; --) shift ;; -*) echo "bad option ''" ; exit 1 ;; *) files=("${files[@]}" "") ; shift ;; esac done if [ ${#files} -eq 0 ] ; then echo output file required exit 1 fi [ ! -z "$r" ] && echo "r on" [ ! -z "$v" ] && echo "v on" [ ! -z "$x" ] && echo "x on" [ ! -z "$l" ] && echo "l == $l" echo "output file(s): ${files[@]}"

EDIT: for completeness I have provided an example of handling an option requiring an argument.

编辑:为了完整起见,我提供了一个处理需要参数的选项的示例。

回答by frankc

If you are using getops, just shift by $OPTIND-1after your case statement. Then what is left in $*will be everything else, which is probably what you want.

如果您使用 getops,只需$OPTIND-1在 case 语句后切换即可。那么剩下的$*将是其他一切,这可能是您想要的。

#!/bin/sh
#
#   "@(#)$Id: rcsunco.sh,v 2.1 2002/08/03 07:41:00 jleffler Exp $"
#
#   Cancel RCS checkout

# -V print version number
# -n do not remove or rename checked out file (like SCCS unget) (default)
# -r remove checked out file (default)
# -k keep checked out file as $file.keep
# -g checkout (unlocked) file after clean-up
# -q quiet checkout

: ${RCS:=rcs}
: ${CO:=co}

remove=yes
keep=no
get=no
quiet=

while getopts gknqrV opt
do
    case $opt in
    V)  echo "`basename 
...
shift $(($OPTIND - 1))

case $# in
2)  case  in
    install)    MODE=Installation;;
    uninstall)  MODE=Uninstallation;;
    *)          usage;;
    esac;;
*)  usage;;
esac
.sh`: RCSUNCO Version $Revision: 2.1 $ ($Date: 2002/08/03 07:41:00 $)" | rcsmunger exit 0;; g) get=yes;; k) keep=yes;; n) remove=no;; q) quiet=-q;; r) remove=yes;; *) echo "Usage: `basename
jlss install program
.sh` [-{n|g}][-{r|k}] file [...]" 1>&2 exit 1;; esac done shift $(($OPTIND-1)) for file in $* do rfile=$(rfile $file) xfile=$(xfile $rfile) if $RCS -u $rfile then if [ $keep = yes ] then if [ -f $xfile ] then mv $xfile $xfile.keep echo "$xfile saved in $xfile.keep" fi elif [ $remove = yes ] then rm -f $xfile fi if [ $get = yes ] && [ $remove = yes -o $keep = yes ] then $CO $quiet $rfile fi fi done

回答by Jonathan Leffler

You're are suffering from illusions; using getoptsdoes not require mandatory arguments prefixed by a flag letter. I tried to find a suitable example from my corpus of scripts; this is a semi-decent approximation. It is called rcsuncoand is used to cancel a checkout from RCS. I haven't modified it in a while, I see; I use it quite often (because I haven't migrated from RCS completely, yet).

你正受幻觉的折磨;usinggetopts不需要以标志字母为前缀的强制性参数。我试图从我的脚本语料库中找到一个合适的例子;这是一个半体面的近似值。它被调用rcsunco并用于从 RCS 取消结帐。我有一段时间没有修改它了,我明白了;我经常使用它(因为我还没有完全从 RCS 迁移)。

jlss -d $HOME -u me -g mine -x -p install program

It's only a semi-decent approximation; the script quietly does nothing if you don't supply any file names after the optional arguments. However, if you need to, you can check that the mandatory arguments are present after the 'shift'. Another script of mine does have mandatory arguments. It contains:

这只是一个半体面的近似值;如果在可选参数后不提供任何文件名,脚本将不做任何事情。但是,如果需要,您可以检查“shift”后是否存在强制参数。我的另一个脚本确实有强制参数。它包含了:

git -global option command [-sub option] [...]

So, that command (jlss) can take optional arguments such as -d $HOME, but requires either installor uninstallfollowed by the name of something to install. The basic mode of use is:

因此,该命令 ( jlss) 可以采用可选参数,例如-d $HOME,但需要installuninstall后跟要安装的东西的名称。基本的使用方式是:

##代码##

But the optional mode is:

但可选模式是:

##代码##

I didn't show all of jlssbecause it has about 12 options - it isn't as compact as rcsunco.

我没有全部展示,jlss因为它有大约 12 个选项——它不像rcsunco.



If you were dealing with mandatory arguments before option arguments, then you'd have to do a bit more work:

如果您在选项参数之前处理强制参数,那么您必须做更多的工作:

  • You'd pick up the mandatory arguments, shifting them out of the way.
  • Then you process the optional arguments with the flags.
  • Finally, if appropriate, you handle the extra 'file name' arguments.
  • 你会拿起强制性的论点,把它们移开。
  • 然后用标志处理可选参数。
  • 最后,如果合适,您可以处理额外的“文件名”参数。

If you are dealing with mandatory arguments interspersed with option arguments (both before and after the mandatory ones), then you have still more work to do. This is used by many VCS systems; CVS and GIT both have the facility:

如果您正在处理穿插有选项参数的强制参数(在强制参数之前和之后),那么您还有更多工作要做。许多 VCS 系统都使用它;CVS 和 GIT 都具有以下功能:

##代码##

Here, you run one getoptsloop to get the global options; pick up the mandatory arguments; and run a second getoptsloop to get the sub-options (and maybe run a final loop over the 'file name' arguments).

在这里,您运行一个getopts循环来获取全局选项;拿起强制性参数;并运行第二个getopts循环以获取子选项(并且可能对“文件名”参数运行最终循环)。

Isn't life fun?

生活不是很有趣吗?

回答by codeholic

And I heard a completely opposite thing, that you shouldn't use getopt, but the getoptsbuiltin.

我听到了一个完全相反的事情,你不应该使用getopt,而是getopts内置的。

Cross-platform getopt for a shell script

用于 shell 脚本的跨平台 getopt

Never use getopt(1). getopt cannot handle empty arguments strings, or arguments with embedded whitespace. Please forget that it ever existed.

The POSIX shell (and others) offer getopts which is safe to use instead.

永远不要使用 getopt(1)。getopt 无法处理空参数字符串或带有嵌入空格的参数。请忘记它曾经存在过。

POSIX shell(和其他)提供了可以安全使用的 getopts。

回答by sam

Here's yet another way to "Option-ize your shell scripts" (whithout using getopt or getopts):

这是“选项化您的 shell 脚本”的另一种方法(不使用 getopt 或 getopts):

http://bsdpants.blogspot.com/2007/02/option-ize-your-shell-scripts.html

http://bsdpants.blogspot.com/2007/02/option-ize-your-shell-scripts.html