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?
"{@:2}"
to not work, which is why the correct answer matches above.
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.
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.
foo=shift
doesn't do what I'd expect.
foo=$(shift)
shift
(in shell) does not have any output. It just discards $1
and shifts everything down. 2) $(...)
starts a subshell, which has its own local arguments. It shifts the arguments in the subshell, which does not affect the parent
somecommand "$1" "${@:2}"
does with this method (i.e. shift "inline") though?
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
bash
. Nevertheless I couldn't use the "minus" elements. It didn't the work for me. Not as expected. Thank you
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
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.
Success story sharing
#!/usr/bin/env sh
That's why I had problems. You example works fine, same as above provided, after I removed that shebang"${@:2}"
instead -- using*
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). The difference isn't noticeable withecho
, but it matters for many other things.echo
is forgiving enough to concatenate them for you; other commands might not be so nice. Don't just use one or the other: learn the difference between*
and@
, and when to use each. You should be using them about equally. A good example of when this will be a problem: if$3
contains a line break (\n
), it will be replaced with a space, provided you have a default$IFS
variable.echo
just used as an example -- in which case they should not be run together. In my experience, situations where you want them run together are rare (see this question for one example), and"$@"
is almost always what you want. Also, the problem you mention with a line break only occurs if$@
(or$*
) isn't in double-quotes.$*
quite often in my scripts.