-1

I want to create a bash script, that gets 3 parameters. But the second needs to be $*, because i need later these lines. The other two parameters (first and third) doesn't need this.

for x in $* do

The first and second parameter aren't the problem, this one works:

parameter1="$1"
shift
parameter2="$*"

But i need the third parameter at the end and something like this

parameter1="$1"
parameter3="$3"
shift
parameter2="$*"

won't work. My command at the end should look like this:

bash myscript parameter1 parameter2 parameter3
  • 1
    Do you mean that you want the first and last parameters, then to loop over all the ones in the middle? – that other guy Jan 24 '17 at 16:44
  • 2
    `$*`, by its nature, captures all your remaining arguments into a single string. **All of them**. – Charles Duffy Jan 24 '17 at 16:53
  • (That also means you lose the distinction between `"one arg" "another arg"` and `one arg another arg` or `"one arg another arg"` -- in general, `$*`'s effects are undesired, and you shouldn't use it as you are here). – Charles Duffy Jan 24 '17 at 16:53
  • 2
    ...which is to say: `for x in $*` is basically always wrong. `for x in "$@"` is the Right Thing. – Charles Duffy Jan 24 '17 at 17:03
  • BTW, I'm not sure that there's enough context here to explain *why* you think you need `$*` or `"$@"`. Instead of `myscript parameter1 parameter2 parameter3`, why not `myscript parameter1 "parameter2" parameter3`, and use `"$2"` for the second one? – Charles Duffy Jan 24 '17 at 18:25
  • BTW, this is closely related to http://stackoverflow.com/questions/20398499/remove-last-argument-from-argument-list-of-shell-script-bash – Charles Duffy Jan 24 '17 at 18:36

2 Answers2

3

For specifically three parameters, you can use substring parameter expansion in a simple way:

parameter1=$1
parameter2="${@:2:1}"   # One parameter, starting with #2
parameter3=$3

Or course, that's unnecessary, since you can just use $2 instead of ${@:2:1}, but I point it out as a simple introduction to the syntax (and not at all because I overlooked the fact you would use $2, really....)

(You can also use it as a substitute for indirect parameter expansion; "${@:n:1}" and "${!n}" are basically equivalent when n is a variable with an integer value.)

For the more general case, where you want an arbitrary number of arguments between the first and last, it gets a little more complicated, although the principle is the same:

parameter1=$1
middleParameters=( "${@:2:$#-2}" )  # n - 2 parameters, starting with #2, i.e., all but $1 and ${!n} for n=$#
lastParameter="${@:$#}"
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Are you sure about "${@:n:1}" and "${!n}" are basically equivalent? I try : set A B C ; B=99 ; echo "${@:2:1}" ; echo "${!2}", and I get "B" on the first line, and 99 on the second line. Did I understand it incorrectly? – Fred Jan 24 '17 at 17:26
  • I should have been more explict; I meant a *literal* variable `n` with an integer value for that equivalency. (I said "basically" because I also didn't bother checking for other differences for things like out-of-bounds indices in each case.) – chepner Jan 24 '17 at 17:58
  • Ok, I tested that, and I see that it works. So, if I understand correctly, if the start index used in the expansion is a literal integer, it is is used as is, but if it is a variable name, it expands to the value of that variable. Am I stating that correctly? – Fred Jan 24 '17 at 18:06
  • Right; the indices are evaluated in an arithmetic context, just as if you had written `${@:$((n)):1}` instead. – chepner Jan 24 '17 at 18:36
2

shift removes an argument from the left. If you want to remove an argument from the right, you can do that with:

set -- "${@:1:$# - 1}"

Thus:

parameter1=$1          # capture leftmost argument
shift                  # remove leftmost argument

parameter3=${*:$#:1}   # capture rightmost argument
set -- "${@:1:$# - 1}" # remove rightmost argument

parameter2=$*          # concatenate remaining arguments and store in a string

Note that $* is almost certainly the Wrong Thing. If you want to keep your arguments separate, respecting their quoting, instead use an array:

parameter2=( "$@" )
for item in "${parameter2[@]}"; do
  echo "Processing item: $item"
done

If your script is run as yourscript arg1 "item A" "item B" arg3, then the above will ensure that item A and item B are treated as individual arguments, rather than treating item as an argument, A as another, etc.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Sorry, but what do you mean with 'argument from the left' or 'right'? Do you mean, that $* ignores with shift the parameter on the left side? – idontknowwhoiamgodhelpme Jan 24 '17 at 20:26
  • @ipo, I mean `shift` removes the old `$1` from the argument list altogether, and makes the thing that was `$2` be `$1`, the thing that was `$3` be `$2`, the thing that was `$4` be `$3`, etc. If before you ran `shift`, `$1` was `one` and `$2` was `two`, then *after* `shift`, then `$1` is `two`, and the `one` is *gone*. – Charles Duffy Jan 24 '17 at 20:50
  • @ipo, ...and to be clear, that's *always* what it does; it's not some kind of magic "remove whatever I just read" operation; instead, it takes things off the left-hand side of the argument list, and *shifts* whatever was to the right of those things over to fill in the vacated positions. – Charles Duffy Jan 24 '17 at 20:54
  • @ipo, ...`$*`, by contrast, takes *the entire argument list* and crams it into one string, with the first character of the IFS variable (by default, a space) injected between those arguments. If you used `shift` to take something out of the argument list, then for that reason that thing will no longer be included in `"$*"`. – Charles Duffy Jan 24 '17 at 20:56
  • @ipo, ...so, you can try this: `set -- one two three; echo $1; shift; echo $1` -- you'll see that the first `echo` emits `one`, and the second one emits `two`, because `$1` changed when `shift` was called. You can replace the `$1`s with `$*`s to see the impact there as well. – Charles Duffy Jan 24 '17 at 20:57