unshift args after calling shift 1

6,466

Solution 1

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

Solution 2

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.

Solution 3

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

Share:
6,466

Related videos on Youtube

Alexander Mills
Author by

Alexander Mills

Updated on September 18, 2022

Comments

  • Alexander Mills
    Alexander Mills almost 2 years

    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.

  • ilkkachu
    ilkkachu almost 6 years
    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
  • Kusalananda
    Kusalananda almost 6 years
    @ilkkachu Yes, that might be right. In that case a check at the top for [ "$#" -ge 1 ] or something similar might be in order.
  • Dennis
    Dennis almost 6 years
    "$@" is better than "$1" "${@:2}", which will create an extra argument if $@ is empty.
  • Kusalananda
    Kusalananda almost 6 years
    @muru Thanks! I left my Swiss army knife of standard substitutions and expansions elsewhere it seems. Thanks for reminding me!