unshift args after calling shift 1

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.


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

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 ;).