163

I'm writing a bash script that needs to loop over the arguments passed into the script. However, the first argument shouldn't be looped over, and instead needs to be checked before the loop.

If I didn't have to remove that first element I could just do:

for item in "$@" ; do
  #process item
done

I could modify the loop to check if it's in its first iteration and change the behavior, but that seems way too hackish. There's got to be a simple way to extract the first argument out and then loop over the rest, but I wasn't able to find it.

Herms
  • 37,540
  • 12
  • 78
  • 101

4 Answers4

177

Use shift.

Read $1 for the first argument before the loop (or $0 if what you're wanting to check is the script name), then use shift, then loop over the remaining $@.

echo "$@";  # will echo all args
shift;  # will remove first arg from the "$@"
echo "$@";  # will echo all args except first one
valex
  • 5,163
  • 2
  • 33
  • 40
Amber
  • 507,862
  • 82
  • 626
  • 550
170

Another variation uses array slicing:

for item in "${@:2}"
do
    process "$item"
done

This might be useful if, for some reason, you wanted to leave the arguments in place since shift is destructive.

Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • Exactly what I wanted. Now don't need temp variable for dereferencing first argument in `"${!1}${@:2}"` – Charlie Gorichanaz Mar 27 '17 at 22:59
  • @Herms this should be the accepted answer, more readable and not destructive (vs shift) – user1075613 Apr 03 '17 at 20:17
  • 1
    Technically the currently accepted answer is correct, but this is technically as well as practically correct (since shift is destructive). This is a better answer. So Ideally should be the answer. – Kuldeep Dhaka Aug 04 '18 at 16:32
  • For those who wonder, why `shift` is destructive: It actually removes the element ultimately from the arguments list. Ref: https://unix.stackexchange.com/a/174568/254204 – pico_prob Apr 30 '21 at 21:28
62
firstitem=$1
shift;
for item in "$@" ; do
  #process item
done
twasbrillig
  • 17,084
  • 9
  • 43
  • 67
nos
  • 223,662
  • 58
  • 417
  • 506
6
q=${@:0:1};[ ${2} ] && set ${@:2} || set ""; echo $q

EDIT

> q=${@:1}
# gives the first element of the special parameter array ${@}; but ${@} is unusual in that it contains (? file name or something ) and you must use an offset of 1;

> [ ${2} ] 
# checks that ${2} exists ; again ${@} offset by 1
    > && 
    # are elements left in        ${@}
      > set ${@:2}
      # sets parameter value to   ${@} offset by 1
    > ||
    #or are not elements left in  ${@}
      > set ""; 
      # sets parameter value to nothing

> echo $q
# contains the popped element

An Example of pop with regular array

   LIST=( one two three )
    ELEMENT=( ${LIST[@]:0:1} );LIST=( "${LIST[@]:1}" ) 
    echo $ELEMENT
Tegra Detra
  • 24,551
  • 17
  • 53
  • 78
  • Please also explain the code to be more educative. – László Papp Apr 09 '14 at 04:11
  • `q=${@:0:1}` (btw, you explanation misquotes it as `q=${@:1}`) should be `q=${@:1:1}` to be clearer : `$@` starts with index *1*, presumably to parallel the explicit `$1`, `$2`, ... parameters - element 0 has _no_ value (unlike its explicit counterpart, `$0`, which reflects the shell / script file). `[ ${2} ]` will _break_ should `$2` contain embedded spaces; you won't have that problem if you use `[[ ${2} ]]` instead. That said, the conditional and the `||` branch are not needed: if there is only 1 argument, `${@:2}` will simply expand to the empty string (cont'd). – mklement0 Apr 09 '14 at 05:05
  • (cont'd) You should, however, use `set --` so as to ensure that arguments that happen to look like options are not interpreted as such by `set` and you should double-quote the `${@:2}` reference and `$q` reference in the `echo` statement. At this point we get: `q=${@:1:1}; set -- "${@:2}"; echo "$q"`. However, `q=${@:1:1}` is just another (more cumbersome) way of saying `$1`, and the rest essentially re-implements the `shift` command; using these features we simply get: `q=$1; shift; echo "$q"`. – mklement0 Apr 09 '14 at 05:08
  • @mklement0 I was wonder about where a good place to place a printf to clean the paths ? ( I wish I could be clearer ) – Tegra Detra Apr 09 '14 at 06:26
  • Unfortunately, I don't understand. What paths? Clean in what way? – mklement0 Apr 09 '14 at 13:38