DEV Community

Yasuhiro Yamada
Yasuhiro Yamada

Posted on • Edited on

Why ${1+"$@"} is used in shell script

Sometimes, I use ${1+"$@"} instead of "$@" when passing arguments to a function or an external command.
Here is the example.

#!/bin/bash

main () {
  # Something...
}

main ${1+"$@"}

${1+"$@"} behaves almost same as "$@"(If you do not understand "$@", see this helpful article).

However, there are many shell scripts that include ${1+"$@"} instead of "$@" all over the world.

Oops, have you never seen them ?

OK, let's execute this command on your machine.

$ grep -a -F '${1+"$@"}' /bin/* /usr/bin/*

You'll get bunch of results on either Linux or BSD.

$ grep -a -F '${1+"$@"}' /bin/* /usr/bin/*
/bin/c2ph:    eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
/bin/c89:exec gcc $fl ${1+"$@"}
/bin/c99:exec gcc $fl ${1+"$@"}
/bin/catchsegv:"$prog" ${1+"$@"} 2>&3 3>&-)
/bin/find2perl:    eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
...
...
/usr/bin/xzgrep:eval "set -- $operands "'${1+"$@"}'
/usr/bin/zgrep:    eval "set -- $arg2 "'${1+"$@"}'
/usr/bin/zgrep:eval "set -- $operands "'${1+"$@"}'

And I know some OSS projects use this expression.
In addition, I am also using it in my own projects.

Let me note my reason of why I'd rather use ${1+"$@"} than "$@".
I am little bit concern about whether my purpose is reasonable or not.
Because historical background related to grammatical expression of code is very hard to find on the web (Because it's difficult to search).
If you use ${1+"$@"} and have any other intention, please comment and let me know!

My conclusion first

Why use it?

  • To improve portability
  • "$@" does not work as expected on the particular environments.
    • Early versions of Bourne shell
    • Particular versions of Bash

Is it better to use it?

Depending on your situation.

  • You do not need to use ${1+"$@"} if you can ensure that "$@" expands 1 or more positional parameters.
  • It's better to use it if the number of positional parameters of "$@" can be 0.
  • But I think, you should not pay large cost to use it.

What ${1+"$@"} does ?

This expression is came from the variable expansion of ${parameter+word} provided by Bourne shell.

Bourne Shell Manual, Version 7

${parameter+word}
If parameter is set then substitute word; otherwise
substitute nothing.

1 is located to parameter's position, and "$@" is located to the word's one.

As a result, ${1+"$@"} means...
If $1 is defined, evaluate "$@".

This expression can be used in the not only Bourne shell, but also Bourne Again Shell (Bash).
It seems that this is not documented on the Bash's manual. But you can find it if you read carefully.

Bash has ${var:+word} variable expansion and its colon can be omitted.

Bash Reference Manual

if the colon is omitted, the operator tests only for existence.

Normally, ${var:+word} checks whether the var is empty or not.
But if colon is ommited, it just checks var is defined or not.
Therefore, it behaves completely same as Bourne shell's ${parameter+word}

What's happened with "$@" ?

Here is simple shell script. It just prints the arguments by echo.
If any undefined variable is used, the script exits unsuccessfully because set -u is stated.
But it seems that there is no undefined variable as you may know.

myecho.sh

set -u
echo "$@"

Bash 3.2

Try it on the old version of Bash first.

~ $ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)
Copyright (C) 2007 Free Software Foundation, Inc.

It works as expected.

$ /bin/sh myecho.sh A B C
A B C

$ /bin/sh myecho.sh
### empty result

Bourne shell on V7

Next, let's test in on Bourne shell.
But what is "Bourne shell" ? (I do not know honestly)
Let's check Wikipedia :p

Bourne shell - Wikipedia

The Bourne shell was the default shell for Version 7 Unix.

OK, let's use Version 7 Unix (called V7 in this article).
V7 can be simulated on SIMH (I used the docker container alpine-simh).
Off course, vi is not installed to the V7 as myecho.sh must be created by ed.

$ ed myecho.sh
a
set -u
echo "$\@
"
.
w
17
q

$ cat myecho.sh
set -u
echo "$@"

And test it.

$ /bin/sh myecho.sh A C D
A B C

$ /bin/sh myecho.sh
myecho.sh: @: parameter not set

Exit with error !

But, is it big issue for you ?
Many of you may think that everybody no longer uses Bourne shell on V7.
But see next.

Bash 4.0.0

I built Bash 4.0.0.

$ /usr/bin/bash --version
GNU bash, version 4.0.0(1)-release (x86_64-unknown-linux-gnu)
Copyright (C) 2009 Free Software Foundation, Inc.

And test this script.
Result is like this.

$ /usr/bin/bash myecho.sh A B C
A B C

$ /usr/bin/bash myecho.sh
myecho.sh: line 2: $@: unbound variable

Exit with error again !
Particular version of Bash does not work as well.

However, using ${1+"$@"} instead of "$@", we can avoid above errors.

History of this issue

By the way, who started to use ${1+"$@"} ?
I am not sure, but there is the discussion in the book called The UNIX-HATERS Handbook published in 1994.
Regarding this book, shell experts had used this expression as of 1991.

Milt> what does the “${1+“$@”}” mean? I’m sure it’s to
Milt> read in the rest of the command line arguments, but
Milt> I’m not sure exactly what it means

It says the reason is ...

If we used only “$@” then that would substitute to “” (a null argu-
ment) if there were no invocation arguments, but we want no argu-
ments reproduced in that case, not “”.

Let me explain this part in detail.
Generally (and grammatically), "$@" is supposed to be handled as NOTHING if the number of arguments is 0.

my_command "$@"

### Same as ...
my_command

However, "$@" is handled as empty string "" on the particular environments like Bourne shell on V7.

my_command "$@"

### Same as this !
my_command ""

This is unexpected behavior for many developers, I guess.
On the other hand, ${1+"$@"} can be NOTHING as expected.

my_command ${1+"$@"}

### same as below
my_command

And the book says that this way is compatible with V7.

I think ${1+“$@”} is portable all the way back to “Version 7 Unix.”

Investigation on V7

I checked this behavior on V7.
Firstly, create two files as followings.

main1.sh

./sub.sh "$@"

sub.sh

echo "# = $#"
echo "@ = $*"

( I could not unify them into single file as shell's "function" was not suppored as of V7. )

The result of main1.sh is like this.

$ /bin/sh main1.sh A B C
# = 3
@ = A B C

$ /bin/sh main1.sh
# = 1
@ =

$# is 1. Because empty string "" is passed.

Next, create main2.sh that uses ${1+"$@"} instead of $@.

main2.sh

./sub.sh ${1+"$@"}

The result is ...

$ /bin/sh main2.sh A B C
# = 3
@ = A B C

$ /bin/sh main2.sh
# = 0
@ =

$# is 0.
Because NOTHING is passed.

Investigation on Bash 4.0.0

Personally, to keep compatibility with particular versions of Bash is main reason to use this expression.

As I mentioned above, "$@" is not equal to ${1+"$@"} in Bash 4.0.0 as same as Bourne shell.

(I noticed this bug when I was running automated testing for my personal project.)

Strangely, "$@" is going to be NOTHING.
This is different from Bourne shell.

$ /usr/bin/bash main1.sh
# = 0
@ =
$ /usr/bin/bash main2.sh
# = 0
@ =

However, the script is going to be failed with set -u as Bash recognizes that unbound variable is used.

$ /usr/bin/bash -u main1.sh
main1.sh: line 1: $@: unbound variable

$ /usr/bin/bash -u main2.sh
# = 0
@ =

This bug can also be avoided by ${1+"$@"}.
It was already fixed in later versions.
But I know Bash had got many bugs historically.
For example, parameter expansion still has bugs as of bash 5.0.3.

$ bash -c 'set -u; typeset -a a; echo "${#a[@]}"'
bash: a: unbound variable

(this bug was already reported by @satoh_fumiyasu)

The reason why I am still using ${1+"$@"} is mainly came from the concerns about the Bash's robustness.
Honestly, I am not interested in the compatibility for early version of Bourne shell (It does not support even the Shebang !).

Conclusion

As I mentioned, ${1+"$@"} might NOT be equal to "$@" if the number of positional parameters is 0.
Therefore, it's not necessary to use if the number of parameter is supposed to be 1 or more (You would rather NOT use it as it will cause confusion).

If the number of parameters can be 0, it's worth using the expression.
Bash can recognize it grammatically.

However, the environment that has this issue is quite rare as of 2019.

  • This behavior is fixed as of 1986 in SVR3 shell (I guess).

Bourne shell - Wikipedia

Features introduced after 1979
...
Modern "$@" – SVR3 shell (1986)

Off course, there is no technical concern about use of ${1+"$@"}.
On the other hand, I think it is not worth paying attention and large cost to adopt this expression.

Top comments (2)

Collapse
 
coreyja profile image
Corey Alexander

This was a great read! I hadn't seen ${1+"$@"} before so this was a fun thing to learn!

It's interesting that it's not just a historical thing and also affects more recent versions of BASH as well!

I'll definitely at least think about this and decide which to use next time I am writing a BASH script that may or may not take arguments!

Collapse
 
perlackline profile image
セラ

I came here while looking up the second line of code created by find2perl. This article was very easy to understand and interesting. Thank you.
And maybe s/myecho.sh A C D/myecho.sh A B C/ in "Bourne shell on V7", I guess.