11

I have this scenario

first_arg="$1";

if foo; then
    shift 1;
    node foo.js "$@"

elif bar; then

   shift 1;
    node bar.js "$@"

elif baz; then

   shift 1;
   node baz.js "$@"

else

   node default.js "$@"

fi

I would like to turn the above into this:

first_arg="$1";
shift 1;

if foo; then

    node foo.js "$@"

elif bar; then

    node bar.js "$@"

elif baz; then

   node baz.js "$@"

else

   unshift 1;
   node default.js "$@"

fi

but I am not sure if there is an operator like unshift, which I just made up. One workaround might be this:

node default.js "$first_arg" "$@"

but when I tried that, I got weird behavior.

muru
  • 72,889

3 Answers3

17

You never need to use shift 1 in the first place. Just use the positional arguments and slice around their indices to pass the arguments.

first_arg="$1"

Once you do this, the rest of the arguments can be accessed as "${@:2}". The notation is a way to represent from positional argument 2 to till the end of the list.

Using the construct for your example would be to do

node foo.js "${@:2}"

and for the final else part do as

node default.js "$1" "${@:2}"

which is the same as doing "$@" as there is no positional argument shift done.

Inian
  • 12,807
  • 6
    "$@" is better than "$1" "${@:2}", which will create an extra argument if $@ is empty. – Dennis Jul 14 '18 at 04:41
  • Unfortunately shift 1 is needed if your args have quotes in them. It seems that bash treats "$@" and "${@}" in a special way, allowing it correctly pass through quoted args (like arg1 "arg two" arg3), see: https://stackoverflow.com/a/53214779/274503 – Yarek T May 25 '23 at 16:08
11

You could save your arguments in an array:

args=( "$@"  )  # use double quotes
shift 1

if foo; then
  node foo.js "$@"
elif bar; then
  node bar.js "$@"
elif baz; then
  node baz.js "$@"
else
  node default.js "${args[@]}"
fi
jesse_b
  • 37,005
10

The thing you tried:

first_arg=$1
shift

# ...later...

else
    node default.js "$first_arg" "$@"
fi

This would have been identical to your first variant provided that there are at least one command line argument.

With no command line arguments "$first_arg" would still be an empty string, and therefore an argument, whereas "$@" would not generate even an empty string.

If your Node application accepts an empty argument on the command line, then this may make the application's behave different in your two code variants.

If calling your script with no command line arguments is a valid thing to do, you could do

node default.js ${first_arg:+"$first_arg"} "$@"

where ${first_arg:+"$first_arg"} would expand to nothing if first_arg is empty or unset, but to "$first_arg" if first_arg was set to a non-empty string. Or, if you want to cause an explicit failure for an unset or empty variable:

node default.js "${first_arg:?Error, no command line arguments}" "$@"

Alternatives includes making a copy of $@ as a bash array as Jesse_b shows in his answer.

Putting $first_arg back into $@ with

set -- "$first_arg" "$@"`

would not work as this would include an empty argument as $1 in $@ if the command line argument was missing.

With

set -- ${first_arg:+"$first_arg"} "$@"`

you would "unshift" nothing if $first_arg was empty or if the variable did not exist.


Note that shift is the same as shift 1 and that single statements don't need to be terminated by ; unless followed by another statement on the same line (newline is a command terminator, just like ;).

Kusalananda
  • 333,661
  • But if there are no arguments ($1 is unset), then "$first_arg" will expand to an empty string, and the result is different than when shift is omitted and "$@" used – ilkkachu Jul 13 '18 at 18:38
  • @ilkkachu Yes, that might be right. In that case a check at the top for [ "$#" -ge 1 ] or something similar might be in order. – Kusalananda Jul 13 '18 at 18:42
  • 1
    There's a simpler way around "$first_arg" possibly becoming an empty argument: ${first_arg:+"$first_arg"} (https://unix.stackexchange.com/a/415992/70524) – muru Jul 14 '18 at 12:56
  • @muru Thanks! I left my Swiss army knife of standard substitutions and expansions elsewhere it seems. Thanks for reminding me! – Kusalananda Jul 14 '18 at 14:18