1

I have the bash script below:

#!/bin/bash
#
[ $# -eq 1 -a $1 = "--help" -o $# -eq 0 ] && {
  echo Help will come here
}

When I run it:

$ ./script 
./script: line 3: [: too many arguments
$ ./script --help
Help will come here

As you can see, when I don't pass parameters ( $# -eq 0 ) it fails with "too many arguments". So, I tested it directly in terminal:

$ a=1;b=2;c=3
$ [ $a -eq 1 -a $b -eq 2 -o $c -eq 3 ] && echo ok
ok
$ [ $a -eq 0 -a $b -eq 2 -o $c -eq 3 ] && echo ok
ok
$ [ $a -eq 0 -a $b -eq 0 -o $c -eq 3 ] && echo ok
ok
$ [ $a -eq 0 -a $b -eq 0 -o $c -eq 0 ] && echo ok
$ [ $a -eq 0 -a $b -eq 2 -o $c -eq 0 ] && echo ok
$ [ $a -eq 1 -a $b -eq 2 -o $c -eq 0 ] && echo ok
ok

So, if it works perfectly in terminal why doesn't it work passing parameters?

Thanks,

Cyrus
  • 84,225
  • 14
  • 89
  • 153
Adriano_Pinaffo
  • 1,429
  • 4
  • 23
  • 46

4 Answers4

3

Express your condition like this :

[ $# -eq 1 ] && [ "$1" = "--help" ] || [ $# -eq 0 ]

Actually, [ is a command, and the following elements in the commands are subject to word splitting. If an argument is empty (or contains whitespace and is unquoted), you can run into surprises. Using -a and -o is deprecated.

Please note that, if you want to use the && logical operator (instead of an if statement) before your echo statement, you will need to enclose the above inside braces, else the operator precedence (coupled with lazy evaluation) may yield incorrect results.

{ [ $# -eq 1 ] && [ "$1" = "--help" ] || [ $# -eq 0 ] ; } && { echo...

If you do not mind using Bash-specific syntax, you could also write :

[[ $# -eq 1 && $1 = "--help" || $# -eq 0 ]]

Note that in this case, double-quoting $1 is not required, because the [[ ]] construct is special shell syntax, not a command, and what is inside is not subject to word splitting. Because there is a single test, you do not need to enclose it inside braces before your && { echo....

Fred
  • 6,590
  • 9
  • 20
  • Re: "[[ ]] construct is special shell syntax, not a command, and what is inside is not subject to word splitting", is perhaps not entirely correct. https://github.com/koalaman/shellcheck/wiki/SC2048. In shellcheck.net using an unquoted `$@` or `$*` prompts this warning while used inside `[[ ]]`, so this may be an exception. – l'L'l Apr 09 '17 at 18:59
  • @l'L'l From the Bash manual : "Word splitting and filename expansion are not performed on the words between the [[ and ]]" – Fred Apr 09 '17 at 19:03
  • It's interesting that the warning comes up on shellcheck.net; I wonder if that should be reported. – l'L'l Apr 09 '17 at 19:03
  • Still from the manual, in the section about `$@` : "When the expansion occurs within double quotes, each parameter expands to a separate word.". This invaluable behavior, also available for array expansions, can be a little counter-intuitive at first, because double quotes usually enclose a string that will be considered a single word. It should be understood as "expand all arguments, but make sure each argument stays a single word by not submitting it to word splitting". – Fred Apr 09 '17 at 19:07
  • In shellcheck when trying to use `[[ $@ ]]` it comes back with `^-- SC2199: Arrays implicitly concatenate in [[ ]]. Use a loop (or explicit * instead of @).`, and then while using `[[ $* ]]` it returns `^-- SC2048: Use "$@" (with quotes) to prevent whitespace problems.`, so it is a bit confusing. – l'L'l Apr 09 '17 at 19:10
  • I do not remember using `$@` inside a test (quoted or not), and I am not sure it would make sense to do so... But if I try `set -- 1 2 3 ; [[ "$@" ]]` I get no error, whereas `[[ 1 2 3 ]]` fails. So "$@" is either never expanded to several words, or is first but then is concatenated (could be linked to the shellckeck warning). – Fred Apr 09 '17 at 19:12
  • I would say the moral of the story is : do not use `$@` in tests, perform an assignment outside the test first to get what you actually want inside a string, and then perform the test on that. – Fred Apr 09 '17 at 19:13
1

Your entire expression can be simplified to:

function help () {
    printf "%s\n" "help is on it's way."
}

[[ $# -eq 0 || "$*" = "--help" ]] && help ; echo "done." && exit 0 ;

This checks if the total sum of arguments is zero, or the argument(s) equals "--help". If either of those two things are true then it proceeds to the help function, otherwise echo "done" and exit.

l'L'l
  • 44,951
  • 10
  • 95
  • 146
  • This works beautifully @l'L'l. I don't quite understand the difference between [] and [[]]. From what I know [] is a short for the command test. – Adriano_Pinaffo Apr 11 '17 at 02:54
  • @Adriano_epifas: In short, `[[` is bash's improvement to the `[` notation. Here's a detailed comparison: http://mywiki.wooledge.org/BashFAQ/031. Examples: http://stackoverflow.com/a/3427931/499581 – l'L'l Apr 11 '17 at 03:13
  • You're welcome! `[[` and `[` are actually "test commands" not really "notation" as mentioned... ;_; – l'L'l Apr 11 '17 at 03:23
0

When you are executing the script without parameter, you are getting the error because your condition is matching with blank character, see below -

$sh -x kk.sh 
+ '[' 0 -eq 1 -a = --help -o 0 -eq 0 ']'
kk.sh: line 3: [: too many arguments

As you can see that there is no value to match.

When you will execute below command in your terminal -

$[ $a -eq 1 -a $b -eq 2 -o $c -eq 3 ] && echo ok
-bash: [: too many arguments
$a=1;b=2;c=3
$[ $a -eq 1 -a $b -eq 2 -o $c -eq 3 ] && echo ok
ok ####it is printing this value bcoz you have set the variable to match, it doesn't matter condition is wrong or right but there is something to match.

To resolve this issue you can use one if condition in the beginning to assign a dummy value if there is no value.

VIPIN KUMAR
  • 3,019
  • 1
  • 23
  • 34
-1

Try this :-

[ $# -eq 1 ] || [ $1 = "--help" ] || [ $# -eq 0 ]

This way it will automatically display if --help is provided or 1 is typed.

I think $# is creating the problem as both there is $# for first and second conditions

Shubhraj
  • 21
  • 5
  • `$1` should be quoted, because an empty value or one with whitespace would cause the test to fail due to an incorrect number of arguments after word splitting. – Fred Apr 09 '17 at 18:06
  • Reuse of `$#` in two conditions is not the problem here, it is actually required to get the behavior I assume is expected by the OP. – Fred Apr 09 '17 at 18:09
  • The logical operator between the first two tests should be `&&`. – Fred Apr 09 '17 at 18:09
  • what @Fred said is right. the problems was that I didn't quote $1. Thank you – Adriano_Pinaffo Apr 11 '17 at 02:47