516
votes

I have a simple script where the first argument is reserved for the filename, and all other optional arguments should be passed to other parts of the script.

Using Google I found this wiki, but it provided a literal example:

echo "${@: -1}"

I can't get anything else to work, like:

echo "${@:2}"

or

echo "${@:2,1}"

I get "Bad substitution" from the terminal.

What is the problem, and how can I process all but the first argument passed to a bash script?

5
To call out for anyone else confused, the wrong shebang was provided causing "{@:2}" to not work, which is why the correct answer matches above.Guvante
You just used default shell, which is dash on Ubuntu and many other Linuxes. In dash "${@: -1}" is interpreted as: {parameter:-word} - Use Default Values, and use word if the parameter is not defined or null. So in dash "${@: -1}" results exactly the same as "$@". To use bash just use the following first line in the script file: #!/bin/bashluart

5 Answers

771
votes

Use this:

echo "${@:2}"

The following syntax:

echo "${*:2}"

would work as well, but is not recommended, because as @Gordon already explained, that using *, it runs all of the arguments together as a single argument with spaces, while @ preserves the breaks between them (even if some of the arguments themselves contain spaces). It doesn't make the difference with echo, but it matters for many other commands.

204
votes

If you want a solution that also works in /bin/sh try

first_arg="$1"
shift
echo First argument: "$first_arg"
echo Remaining arguments: "$@"

shift [n] shifts the positional parameters n times. A shift sets the value of $1 to the value of $2, the value of $2 to the value of $3, and so on, decreasing the value of $# by one.

13
votes

Working in bash 4 or higher version:

#!/bin/bash
echo "$0";         #"bash"
bash --version;    #"GNU bash, version 5.0.3(1)-release (x86_64-pc-linux-gnu)"

In function:

echo $@;              #"p1" "p2" "p3" "p4" "p5"
echo ${@: 0};  #"bash" "p1" "p2" "p3" "p4" "p5"
echo ${@: 1};         #"p1" "p2" "p3" "p4" "p5"
echo ${@: 2};              #"p2" "p3" "p4" "p5"
echo ${@: 2:1};            #"p2"
echo ${@: 2:2};            #"p2" "p3"
echo ${@: -2};                       #"p4" "p5"
echo ${@: -2:1};                     #"p4"

Notice the space between ':' and '-', otherwise it means different:

${var:-word} If var is null or unset,
word is substituted for var. The value of var does not change.

${var:+word} If var is set,
word is substituted for var. The value of var does not change.

Which is described in:Unix / Linux - Shell Substitution

3
votes

http://wiki.bash-hackers.org/scripting/posparams

It explains the use of shift (if you want to discard the first N parameters) and then implementing Mass Usage

0
votes

Came across this looking for something else. While the post looks fairly old, the easiest solution in bash is illustrated below (at least bash 4) using set -- "${@:#}" where # is the starting number of the array element we want to preserve forward:

    #!/bin/bash

    someVar="${1}"
    someOtherVar="${2}"
    set -- "${@:3}"
    input=${@}

    [[ "${input[*],,}" == *"someword"* ]] && someNewVar="trigger"

    echo -e "${someVar}\n${someOtherVar}\n${someNewVar}\n\n${@}"

Basically, the set -- "${@:3}" just pops off the first two elements in the array like perl's shift and preserves all remaining elements including the third. I suspect there's a way to pop off the last elements as well.