检查传递给Bash脚本的参数数量

981
我希望我的Bash脚本在必要的参数数量不足时打印错误消息。
我尝试了以下代码: ```bash if [ "$#" -ne 2 ]; then echo "Error: Invalid number of arguments." fi ```
#!/bin/bash
echo Script name: $0
echo $# arguments 
if [$# -ne 1]; 
    then echo "illegal number of parameters"
fi

因为某种未知的原因,我遇到了以下错误:

test: line 4: [2: command not found

我做错了什么?


75
你不应该把你的脚本命名为“test”。那是一个标准的Unix命令名称,你不想掩盖它。 - Barmar
28
在Bash中,if语句中的'['('[[')和'('('((')两侧应使用空格。 - zoska
6
补充一下 @zoska 的评论,你需要在 [ 前面加一个空格,因为它被实现为一个命令,尝试使用 'which ['。 - Daniel Da Cunha
1
以下是关于编程的内容,翻译成中文:请查看下面链接中提供的更好的示例:https://dev59.com/Y2855IYBdhLWcg3wcz1h - ramkrishna
4
只要不在路径上,把它命名为 test 应该是可以的,对吧,@Barmar? - user253751
显示剩余4条评论
11个回答

1494

就像其他简单命令一样,[...]test 命令需要在参数之间添加空格。

if [ "$#" -ne 1 ]; then
    echo "Illegal number of parameters"
fi

或者

if test "$#" -ne 1; then
    echo "Illegal number of parameters"
fi

建议

在Bash中,建议使用[[ ]],因为它不会对其变量进行单词拆分和路径名展开,所以除非它是表达式的一部分,否则可能不需要引用。

[[ $# -ne 1 ]]

它还具有一些其他功能,例如未引用条件分组、模式匹配(使用 extglob 进行扩展模式匹配)和正则表达式匹配。

以下示例检查参数是否有效。它允许一个或两个参数。

[[ ($# -eq 1 || ($# -eq 2 && $2 == <glob pattern>)) && $1 =~ <regex pattern> ]]

对于纯算术表达式,有些人可能仍然认为使用(( ))更好,但是在[[ ]]中也可以使用其算术运算符,如-eq-ne-lt-le-gt-ge,将表达式作为单个字符串参数放置即可:

A=1
[[ 'A + 1' -eq 2 ]] && echo true  ## Prints true.

如果您需要将其与[[ ]]的其他功能结合使用,这应该很有帮助。

请注意,[[ ]](( ))是与ifcasewhilefor具有相同解析级别的关键字。

正如Dave建议的那样,错误消息最好发送到stderr,以便在stdout被重定向时不被包含:

echo "Illegal number of parameters" >&2

退出脚本

当向脚本传递无效参数时,使其退出是合乎逻辑的。这已经在评论中由ekangas建议过,但某人编辑了此答案并将其返回值更改为-1,所以我也可以做正确的事情。

-1虽然被Bash接受为exit的参数,但它没有明确文档说明,并且不应作为常规建议使用。 64也是最正式的值,因为它在sysexits.h中定义为#define EX_USAGE 64 /* command line usage error */。像ls这样的大多数工具在出现无效参数时也返回2。 我过去也在我的脚本中返回2,但最近我不再关心,只是在所有错误中简单地使用1。但是让我们把2放在这里,因为它是最常见的,可能不特定于操作系统。

if [[ $# -ne 1 ]]; then
    echo "Illegal number of parameters" >&2
    exit 2
fi

参考资料


2
请记住,[只是另一个命令,例如尝试使用which [ - Leo
7
在bash中,命令可以是内置的,也可以不是。[ 是一个内置命令,而 [[ 是一个关键字。在一些旧的shell中,[ 甚至不是内置的。像 [ 这样的命令通常作为外部命令存在于大多数系统中,但是除非你使用 commandexec 来绕过,否则 shell 会优先执行内部命令。请查阅各个shell的文档了解它们的评估方式。注意它们之间的差异,以及在每个shell中它们可能表现出不同的行为。 - konsolebox
1
最后一个建议,我建议在用错误代码退出之前将错误消息写入 STDERR。这样做就可以了:(>&2 echo 'Illegal number of parameters') - Dave
2
@Dave 我同意,但是子shell是不必要的。 - konsolebox
3
为了保持一致性,如果一个字符串不需要被分割成单词或者作为文件名进行扩展,那么无论它的扩展值是否受到这些过程的影响,都应该加上引号。 - konsolebox
显示剩余3条评论

109

如果你要处理数字,使用算术表达式可能是一个好主意。

if (( $# != 1 )); then
    >&2 echo "Illegal number of parameters"
fi

>&2 用于将错误消息写入 stderr。


在当前情况下,为什么这可能是个好主意?考虑效率、可移植性和其他问题,使用最简单和最普遍理解的语法,即 [ ... ],当它能够胜任工作且不需要复杂操作时,难道不是最好的选择吗? - Max
3
@Max 算术扩展 $(( )) 不是很特别,所有 POSIX shells 都应该支持。但是 (( )) 语法(不带 $)并不是其的一部分。如果由于某种原因你受到了限制,那么你肯定可以使用 [ ] 来代替,但请记住,这时你不应该再使用 [[ ]]。我希望你理解 [ ] 的缺点和这些特性存在的原因。但这是一个 Bash 问题,因此我们给出 Bash 答案:[“作为一个经验法则,[ 用于字符串和文件比较。如果要比较数字,请使用算术表达式。”)。 - Aleks-Daniel Jakimenko-A.
在出现错误时,始终写入 STDERR。 (>&2 echo '参数数量不合法') - Dave
1
@Dave 是的,我当时年轻不懂事 :) 已经编辑过了。 - Aleks-Daniel Jakimenko-A.

47

在[]中:!=、=、==...是字符串比较运算符,-eq、-gt...是算术二进制运算符。

我会使用:

if [ "$#" != "1" ]; then
或者:
if [ $# -eq 1 ]; then

11
== 实际上是一个未记录在案的功能,恰好能够与 GNU test 一起使用。它也 恰好 可以在 FreeBSD test 上工作,但可能无法在 foo test 上工作。 值得一提的是,唯一 的标准比较符号是 = - Martin Tournoij
2
这在bash man条目中有记录:当使用==和!=运算符时,运算符右侧的字符串被视为模式,并根据下面描述的模式匹配规则进行匹配。如果启用了shell选项nocasematch,则执行匹配时不考虑字母字符的大小写。如果字符串与模式匹配(==)或不匹配(!=),则返回值为0,否则为1。模式的任何部分都可以加引号强制将其作为字符串匹配。 - jhvaras
2
@jhvaras:这正是Carpetsmoker所说的:它在某些实现中可能有效(确实,在Bash中有效),但它不符合POSIX标准。例如,它将在dash中失败:dash -c '[ 1 == 1 ]'。POSIX仅指定= 而不是== - gniourf_gniourf

37

如果你只对特定参数缺失时退出感兴趣,参数替换是一个很好的选择:

#!/bin/bash
# usage-message.sh

: ${1?"Usage: $0 ARGUMENT"}
#  Script exits here if command-line parameter absent,
#+ with following error message.
#    usage-message.sh: 1: Usage: usage-message.sh ARGUMENT

这里涉及到 Bashism 了吗? - Dwight Spencer
1
@DwightSpencer 这会有影响吗? - konsolebox
@Temak 如果你有具体的问题,我可以回答,但是链接的文章比我更好地解释了它。 - Pat
2
现在当有人针对某个软件(本例中是bash)提出问题,然后其他人抱怨回答问题的答案使用了使生活更轻松但仅适用于该软件的功能时,这只是互联网上的幽默。再见各位,我要回到Google Sheets论坛抱怨他们的回复在我的意大利本地化Office 95版本上无法工作。 - Trinidad

16

可以使用一个简单的一行代码来实现:

[ "$#" -ne 1 ] && ( usage && exit 1 ) || main

这可以分解为:

  1. 测试bash变量中参数的大小,如果不等于1(我们子命令的数量),则执行使用(usage)函数并以状态1退出
  2. 否则,调用main()函数

需要注意的事项:

  • 使用(usage)函数可以只是一个简单的echo“$ 0:params”
  • main函数可以是一个长脚本

1
如果在那行之后还有另一组代码,那就是错误的,因为exit 1只适用于子shell的上下文,这使得它与( usage; false )同义。当涉及到选项解析时,我不喜欢这种简化方式,但你可以使用{ usage && exit 1; }代替。或者可能只需使用{ usage; exit 1; } - konsolebox
1
@konsolebox (usage && exit 1) 对于 ksh、zsh 和 bash 都适用,从 bash 2.0 开始。{...} 语法只在 bash 的 4.0+ 版本中才出现。如果有一种方法对你来说很好用,那没问题,请使用它。但是请记住,并不是每个人都使用与你相同的 bash 实现,我们应该编写符合 posix 标准而非 bashisms 的代码。 - Dwight Spencer
我不确定你在说什么。{...} 是一种常见的语法,几乎所有基于 sh 的 shell 都支持它,即使是那些不遵循 POSIX 标准的旧 shell 也是如此。 - konsolebox

12

看看这份bash cheatsheet,可以帮助很多。

要检查传递的参数长度,您可以使用"$#"

要使用传递的参数数组,您可以使用"$@"

检查长度和迭代的示例:

myFunc() {
  if [[ "$#" -gt 0 ]]; then
    for arg in "$@"; do
      echo $arg
    done
  fi
}

myFunc "$@"

这篇文章对我有帮助,但对我和我的情况来说还缺少了一些东西。希望这能帮到其他人。


1
谢谢。你真是救命恩人。我的情况是我在脚本中创建了函数,并且脚本接受一个参数,该参数在脚本中调用的最后一个函数中使用。再次感谢。 - lallolu

3

以下是一个简单的一行代码,用于检查是否只有一个参数被给出,否则退出脚本:

如果只有一个参数,则输出"最初的回答"。如果没有或多于一个参数,则退出脚本。

[ "$#" -ne 1 ] && echo "USAGE $0 <PARAMETER>" && exit

1
#!/bin/bash

Help() {
  echo "$0 --opt1|-opt1 <opt1 value> --opt2|-opt2 <opt2 value>"
}

OPTIONS=($@)
TOTAL_OPTIONS=$#
INT=0

if [ $TOTAL_OPTIONS -gt 4 ]
then
        echo "Invalid number of arguments"
        Help
        exit 1
fi

while [ $TOTAL_OPTIONS -gt $INT ]
do
        case ${OPTIONS[$INT]} in

                --opt1 | -opt1)
                        INT=`expr $INT + 1`
                        opt1_value=${OPTIONS[$INT]}
                        echo "OPT1 = $opt1_value"
                        ;;

                --opt2 | -opt2)
                        INT=`expr $INT + 1`
                        opt2_value=${OPTIONS[$INT]}
                        echo "OPT2 = $opt2_value"
                        ;;

                --help | -help | -h)
                        Help
                        exit 0
                        ;;

                *)
                        echo "Invalid Option - ${OPTIONS[$INT]}"
                        exit 1
                        ;;

        esac
        INT=`expr $INT + 1`
done

这是我的使用方式,而且没有任何问题

[root@localhost ~]# ./cla.sh -opt1 test --opt2 test2
OPT1 = test
OPT2 = test2

1
这里有很多好的信息,但我想添加一个我认为有用的简单代码片段。
它与上面的一些有什么不同?
  • 将使用情况打印到stderr,比打印到stdout更合适
  • 返回在此其他答案中提到的退出代码
  • 不会将其变成单行代码...
_usage(){
    _echoerr "Usage: $0 <args>"
}

_echoerr(){
    echo "$*" >&2
}

if [ "$#" -eq 0 ]; then # NOTE: May need to customize this conditional
    _usage
    exit 2
fi
main "$@"


0
如果你想要保险起见,我建议使用getopts。
这里有一个小例子:
    while getopts "x:c" opt; do
      case $opt in
        c)
          echo "-$opt was triggered, deploy to ci account" >&2
          DEPLOY_CI_ACCT="true"
          ;;
            x)
              echo "-$opt was triggered, Parameter: $OPTARG" >&2 
              CMD_TO_EXEC=${OPTARG}
              ;;
            \?)
              echo "Invalid option: -$OPTARG" >&2 
              Usage
              exit 1
              ;;
            :)
              echo "Option -$OPTARG requires an argument." >&2 
              Usage
              exit 1
              ;;
          esac
        done

看更多细节示例请点击这里http://wiki.bash-hackers.org/howto/getopts_tutorial


Getopt[s] 仅仅是为了允许相邻的短选项而使事情变得复杂。学会手动解析吧。 - konsolebox

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接