4

On Allow for ${@:2} syntax in variable assignment they say I should not use "${@:2}" because it breaks things across different shells, and I should use "${*:2}" instead.

But using "${*:2}" instead of "${@:2}" is nonsense because doing "${@:2}" is not equivalent to "${*:2}" as the following example:

#!/bin/bash check_args() { echo "\$#=$#" local counter=0 for var in "$@" do counter=$((counter+1)); printf "$counter. '$var', "; done printf "\\n\\n" } # setting arguments set -- "space1 notspace" "space2 notspace" "lastargument"; counter=1 echo $counter': ---------------- "$*"'; counter=$((counter+1)) check_args "$*" echo $counter': ---------------- "${*:2}"'; counter=$((counter+1)) check_args "${*:2}" echo $counter': ---------------- "${@:2}"'; counter=$((counter+1)) check_args "${@:2}" 

-->

GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu) 1: ---------------- "$*" $#=1 1. 'space1 notspace space2 notspace lastargument', 2: ---------------- "${*:2}" $#=1 1. 'space2 notspace lastargument', 3: ---------------- "${@:2}" $#=2 1. 'space2 notspace', 2. 'lastargument', 

If I cannot use "${@:2}" (as they say), what is the equivalent can I use instead?

This is original question Process all arguments except the first one (in a bash script) and their only answer to keep arguments with spaces together is to use "${@:2}"

2
  • How portable do you need your script to be? Is it OK if it's bash-specific? Commented Jun 30, 2019 at 11:15
  • If it is not possible, most portable it can be, the best. It would be nice if it were POSIX portable. Commented Jun 30, 2019 at 16:56

3 Answers 3

6

There's context that's not clear in the question unless you follow the links. It's concerning the following recommendation from shellcheck.net:

local _help_text="${@:2}" ^––SC2124 Assigning an array to a string! Assign as array, or use * instead of @ to concatenate. 

Short answer: Don't assign lists of things (like arguments) to plain variables, use an array instead.

Long answer: Generally, "${@:2}" will get all but the first argument, with each treated as a separate item ("word"). "${*:2}", on the other hand, produces a single item consisting of all but the first argument stuck together, separated by a space (or whatever the first character of $IFS is).

But in the specific case where you're assigning to a plain variable, the variable is only capable of storing a single item, so var="${@:2}" also collapses the arguments down to a single item, but it does it in a less consistent way than "${*:2}". In order to avoid this, use something that is capable of storing multiple items: an array. So:

  • Really bad: var="${@:2}"
  • Slightly less bad: var="${*:2}"
  • Much better: arrayvar=("${@:2}") (the parentheses make this an array)

Note: to get the elements of the array back, with each one treated properly as a separate item, use "${arrayvar[@]}". Also, arrays are not supported by all shells (notably, dash doesn't support them), so if you use them you should be sure to use a bash shebang (#!/bin/bash or #!/usr/bin/env bash). If you really need portability to other shells, things get much more complicated.

Sign up to request clarification or add additional context in comments.

Comments

3

Neither ${@:2} nor ${*:2} is portable, and many shells will reject both as invalid syntax. If you want to process all arguments except the first, you should get rid of the first with a shift.

first="${1}" shift echo The arguments after the first are: for x; do echo "$x"; done 

At this point, the first argument is in "$first" and the positional parameters are shifted down one.

Comments

0

This demonstrates how to combine all ${@} arguments into a single variable one without the hack ${@:1} or ${@:2} (live example):

#!/bin/bash function get_all_arguments_as_single_one_unquoted() { single_argument="$(printf "%s " "${@}")"; printf "unquoted arguments %s: '%s'\\n" "${#}" "${single_argument}"; } function get_all_arguments_as_single_one_quoted() { single_argument="${1}"; printf "quoted arguments %s: '%s'\\n" "${#}" "${single_argument}"; } function escape_arguments() { escaped_arguments="$(printf '%q ' "${@}")"; get_all_arguments_as_single_one_quoted "${escaped_arguments}"; get_all_arguments_as_single_one_unquoted ${escaped_arguments}; } set -- "first argument" "last argument"; escape_arguments "${@}"; 

-->

GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu) quoted arguments 1: 'first\ argument last\ argument ' unquoted arguments 4: 'first\ argument last\ argument ' 

As @William Pursell answer points out, if you would like to get only {@:2} arguments, you can add a shift call before "${@}"

function escape_arguments() { shift; escaped_arguments="$(printf '%q ' "${@}")"; get_all_arguments_as_single_one_quoted "${escaped_arguments}"; get_all_arguments_as_single_one_unquoted ${escaped_arguments}; } 

-->

GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu) quoted arguments 1: 'last\ argument ' unquoted arguments 2: 'last\ argument ' 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.