ChatGPT解决这个技术问题 Extra ChatGPT

Process all arguments except the first one (in a bash script)

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?

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.
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/bash

C
Community

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.


I just realised my shebang was bad: #!/usr/bin/env sh That's why I had problems. You example works fine, same as above provided, after I removed that shebang
Use "${@: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 with echo, but it matters for many other things.
@GordonDavisson The whole point here is to run the arguments together. Were we passing filenames, you would be correct. 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.
@Zenexer: As I understand it, the question was how to pass all but the first argument to "to other part of script", with 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.
@GordonDavisson Hmm... you're right, now that I think about it; the line break shouldn't be an issue. I must have been thinking of something else. However, I still have to disagree about running them together; I need to use $* quite often in my scripts.
E
Esteis

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.


+1: You should probably save $1 into a variable before shifting it away.
Surprisingly foo=shift doesn't do what I'd expect.
I know this is old, but try foo=$(shift)
@raumaankidwai That doesn't work for 2 reasons: 1) 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
Thanks :) Is there any way to do what somecommand "$1" "${@:2}" does with this method (i.e. shift "inline") though?
w
walknotes

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


Your explanation helped a lot and the examples worked pretty fine when using bash. Nevertheless I couldn't use the "minus" elements. It didn't the work for me. Not as expected. Thank you
r
raghav710

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


P
Pavman

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.