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}"
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.
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.
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 '