968

I would like my Bash script to print an error message if the required argument count is not met.

I tried the following code:

#!/bin/bash
echo Script name: $0
echo $# arguments 
if [$# -ne 1]; 
    then echo "illegal number of parameters"
fi

For some unknown reason I've got the following error:

test: line 4: [2: command not found

What am I doing wrong?

konsolebox
  • 72,135
  • 12
  • 99
  • 105
triple fault
  • 13,410
  • 8
  • 32
  • 45
  • 74
    You shouldn't name your script `test`. That's the name of a standard Unix command, you wouldn't want to shadow it. – Barmar Sep 02 '13 at 08:31
  • 28
    Always use spaces around '[' ('[[') or '(' ('((') in if statements in bash. – zoska Sep 02 '13 at 11:35
  • 6
    To add to @zoska comment, you need a space before [ because it is implemented as a command, try 'which ['. – Daniel Da Cunha Feb 14 '14 at 08:00
  • 1
    better example is given on the link below: http://stackoverflow.com/questions/4341630/checking-for-the-correct-number-of-arguments – ramkrishna Jun 26 '14 at 07:10
  • 4
    @Barmar surely naming it `test` is fine as long as it's not on the PATH? – user253751 Oct 18 '14 at 05:30
  • @immibis Yes, that's true. But he has to remember that if he puts it in the PATH it won't work. Avoiding using the name bypasses this problem. – Barmar Oct 19 '14 at 08:10
  • You probably also want to exit with a non-zero exit code after printing a message if the program is called with an illegal number of parameters. This link suggests that some people use the command 'exit 64', where 64 means 'command line usage error'. http://stackoverflow.com/a/1535733/1102730 – ekangas Jul 15 '15 at 19:23
  • A few comments. Make sure you put space between square brackets. bash is picky about that. and make sure #!/bin/bash at the top of file, because some system default to the older bourne shell /bin/sh which is missing some of the syntax of bash shell, but similar enough to mess around with your head. – Bimo Jul 03 '18 at 16:40
  • ker digital ocean publish – SlyOtis Nov 02 '21 at 20:32

11 Answers11

1481

Just like any other simple command, [ ... ] or test requires spaces between its arguments.

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

Or

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

Suggestions

When in Bash, prefer using [[ ]] instead as it doesn't do word splitting and pathname expansion to its variables that quoting may not be necessary unless it's part of an expression.

[[ $# -ne 1 ]]

It also has some other features like unquoted condition grouping, pattern matching (extended pattern matching with extglob) and regex matching.

The following example checks if arguments are valid. It allows a single argument or two.

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

For pure arithmetic expressions, using (( )) to some may still be better, but they are still possible in [[ ]] with its arithmetic operators like -eq, -ne, -lt, -le, -gt, or -ge by placing the expression as a single string argument:

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

That should be helpful if you would need to combine it with other features of [[ ]] as well.

Take note that [[ ]] and (( )) are keywords which have same level of parsing as if, case, while, and for.

Also as Dave suggested, error messages are better sent to stderr so they don't get included when stdout is redirected:

echo "Illegal number of parameters" >&2

Exiting the script

It's also logical to make the script exit when invalid parameters are passed to it. This has already been suggested in the comments by ekangas but someone edited this answer to have it with -1 as the returned value, so I might as well do it right.

-1 though accepted by Bash as an argument to exit is not explicitly documented and is not right to be used as a common suggestion. 64 is also the most formal value since it's defined in sysexits.h with #define EX_USAGE 64 /* command line usage error */. Most tools like ls also return 2 on invalid arguments. I also used to return 2 in my scripts but lately I no longer really cared, and simply used 1 in all errors. But let's just place 2 here since it's most common and probably not OS-specific.

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

References

claymation
  • 2,475
  • 4
  • 27
  • 36
konsolebox
  • 72,135
  • 12
  • 99
  • 105
  • 2
    OP: Keep in mind that `[` is just another command, i.e., try `which [`. – Leo May 03 '19 at 01:33
  • 7
    @Leo Commands can be builtin, and can be not. In bash, `[` is a builtin, while `[[` is a keyword. In some older shells, `[` is not even builtin. Commands like `[` naturally co-exist as an external command in most systems, but internal commands are prioritized by the shell unless you bypass with `command` or `exec`. Check the shell's documentation on how they evaluate. Take note of their difference, and how they may behave differently in every shell. – konsolebox May 05 '19 at 21:26
  • 1
    One last piece, I would suggest writing the error message to STDERR before exiting with an error code. This would do it: `(>&2 echo 'Illegal number of parameters')` – Dave Aug 04 '20 at 08:35
  • 2
    @Dave I agree but the subshell is unnecessary. – konsolebox Aug 04 '20 at 12:34
  • Why are you quoting `$#`? – timgeb Mar 04 '21 at 15:45
  • 3
    @timgeb For consistency. If it doesn't have to undergo word splitting and filename expansion then it should be quoted regardless if its expanded value is expected to be unaffected by such processes or not. – konsolebox Mar 15 '21 at 07:15
  • I always see -eq -ne etc.. inside the if [ ] conditions, but I have no idea what they mean. Could someone point me to somewhere I could find it please? – Adrarc Apr 26 '22 at 17:01
  • @Adrarc First link in References. – konsolebox Apr 27 '22 at 18:52
106

It might be a good idea to use arithmetic expressions if you're dealing with numbers.

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

>&2 is used to write the error message to stderr.

Aleks-Daniel Jakimenko-A.
  • 10,335
  • 3
  • 41
  • 39
  • Why might that be a good idea, in the present case? Considering efficiency, portability and other issues, isn't it best to use the simplest and most universally understood syntax, i.e., `[ ... ]`, when this does the job just fine and no fancy operations are needed? – Max May 09 '20 at 06:33
  • 3
    @Max arithmetic expansions ``$(( ))`` are not fancy and should be implemented by all POSIX shells. However, the `(( ))` syntax (without `$`) is not part of it. If you're for some reason limited, then surely you can use `[ ]` instead, but keep in mind that then you shouldn't use `[[ ]]` also. I hope you understand the pitfalls of `[ ]` and reasons why these features exist. But this was a Bash question so we are giving Bash answers ([“As a rule of thumb, [[ is used for strings and files. If you want to compare numbers, use an ArithmeticExpression”](https://mywiki.wooledge.org/BashFAQ/031)). – Aleks-Daniel Jakimenko-A. May 10 '20 at 08:11
  • On errors, always write to STDERR. `(>&2 echo 'Illegal number of parameters')` – Dave Aug 04 '20 at 08:37
  • 1
    @Dave Yeah. I was young and dumb :) Edited. – Aleks-Daniel Jakimenko-A. Aug 05 '20 at 09:05
47

On []: !=, =, == ... are string comparison operators and -eq, -gt ... are arithmetic binary ones.

I would use:

if [ "$#" != "1" ]; then

Or:

if [ $# -eq 1 ]; then
jhvaras
  • 2,005
  • 21
  • 16
  • 11
    `==` is actually an undocumented feature, which *happens* to work with GNU `test`. It also *happens* to work with FreeBSD `test`, but *may* not work on *foo* `test`. The *only* standard comparison is `=` (just FYI). – Martin Tournoij Jul 22 '14 at 08:31
  • 2
    It is documented on bash man entry: *When the == and != operators are used, the string to the right of the operator is considered a pattern and matched according to the rules described below under Pattern Matching. If the shell option nocasematch is enabled, the match is performed without regard to the case of alphabetic characters. The return value is 0 if the string matches (==) or does not match (!=) the pattern, and 1 otherwise. Any part of the pattern may be quoted to force it to be matched as a string.* – jhvaras Nov 18 '14 at 08:56
  • 2
    @jhvaras: That's exactly what Carpetsmoker said: it _may_ work in some implementations (and indeed, it works in Bash), but [it is not POSIX-compliant](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html#tag_20_128). For example, it will _fail_ with `dash`: `dash -c '[ 1 == 1 ]'`. POSIX only specifies `=`, and not `==`. – gniourf_gniourf Jan 04 '17 at 18:12
37

If you're only interested in bailing if a particular argument is missing, Parameter Substitution is great:

#!/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
Pat
  • 16,515
  • 15
  • 95
  • 114
  • isn't that loaded with bashisms? – Dwight Spencer Apr 28 '16 at 16:23
  • 1
    @DwightSpencer Would it matter? – konsolebox May 17 '16 at 06:42
  • @Temak I can if you have specific questions, but the linked-to article explains it better than I can. – Pat Nov 08 '16 at 21:21
  • 2
    It's just internet humor at this point when someone asks a question specifically about a software (bash in this case) and then people complain about replies that answer the question but use features that make life easier but are exclusive to that software. See you guys, I'm going back to Google Sheets forums to complain that their response doesn't work on my Italian localized version of Office 95. – Trinidad Dec 25 '21 at 06:08
16

A simple one liner that works can be done using:

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

This breaks down to:

  1. test the bash variable for size of parameters $# not equals 1 (our number of sub commands)
  2. if true then call usage() function and exit with status 1
  3. else call main() function

Things to note:

  • usage() can just be simple echo "$0: params"
  • main can be one long script
Dwight Spencer
  • 1,472
  • 16
  • 22
  • 1
    If you have another set of lines after that line, that would be wrong since `exit 1` would only apply to the context of the subshell making it just synonymous to `( usage; false )`. I'm not a fan of that manner of simplification when it comes to options-parsing, but you can use `{ usage && exit 1; }` instead. Or probably just `{ usage; exit 1; }`. – konsolebox May 17 '16 at 06:39
  • 1
    @konsolebox (usage && exit 1 ) works for ksh, zsh and bash going back to bash 2.0. The {...} syntax is only recent to 4.0+ of bash. Don't get me wrong if one way works fine for you then use it but remember not everyone uses the same implementation of bash that you do and we should code to posix standards not bashisms. – Dwight Spencer May 18 '16 at 15:54
  • I'm not sure what you're saying. `{...}` is a common syntax and is available to most if not all shells based on `sh`, even those older shells not following POSIX standards. – konsolebox May 20 '16 at 08:08
12

Check out this bash cheatsheet, it can help alot.

To check the length of arguments passed in, you use "$#"

To use the array of arguments passed in, you use "$@"

An example of checking the length, and iterating would be:

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

myFunc "$@"

This articled helped me, but was missing a few things for me and my situation. Hopefully this helps someone.

jenkizenki
  • 741
  • 6
  • 14
  • 1
    Thanks. You are a life saver. My scenario was that I made functions in my script and the script takes an argument, which is the used in the last function called in the script. Thanks again. – lallolu Sep 07 '21 at 07:09
3

Here a simple one liners to check if only one parameter is given otherwise exit the script:

[ "$#" -ne 1 ] && echo "USAGE $0 <PARAMETER>" && exit
panticz
  • 2,135
  • 25
  • 16
1

There is a lot of good information here, but I wanted to add a simple snippet that I find useful.

How does it differ from some above?

  • Prints usage to stderr, which is more proper than printing to stdout
  • Return with exit code mentioned in this other answer
  • Does not make it into a one liner...
_usage(){
    _echoerr "Usage: $0 <args>"
}

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

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

ngenetzky
  • 111
  • 1
  • 2
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

This is how I am using and it's working without any issue

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

In case you want to be on the safe side, I recommend to use getopts.

Here is a small example:

    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

see more details here for example http://wiki.bash-hackers.org/howto/getopts_tutorial

IsaacE
  • 305
  • 2
  • 10
  • Getopt[s] makes things complicated just for the sake of allowing adjacent short options. Learn to do manual parsing instead. – konsolebox Feb 23 '22 at 06:51
-1

You should add spaces between test condition:

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

I hope this helps.

Fabricio
  • 3,248
  • 2
  • 16
  • 22