166

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.

0

4 Answers 4

184

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
6
  • 12
    See, I knew there was a simple answer. :)
    – Herms
    Commented Apr 23, 2010 at 19:36
  • 1
    Deserves an upvote, but after Dennis' answers get accepted :) Commented Apr 9, 2014 at 3:47
  • @mgarciaisaia seems more appropriate to the OP's requirements: remove the first item
    – Mark Fox
    Commented Oct 7, 2014 at 2:49
  • 3
    this is totally vague- not as helpful as @nos answer below Commented Aug 30, 2016 at 20:18
  • 1
    Would be useful to have an example to avoid having to go to an external link that might disappear one day.
    – Gewthen
    Commented Dec 17, 2020 at 3:37
171

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.

5
  • Exactly what I wanted. Now don't need temp variable for dereferencing first argument in "${!1}${@:2}" Commented Mar 27, 2017 at 22:59
  • @Herms this should be the accepted answer, more readable and not destructive (vs shift) Commented Apr 3, 2017 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. Commented Aug 4, 2018 at 16:32
  • For those who wonder, why shift is destructive: It actually removes the element ultimately from the arguments list. Ref: unix.stackexchange.com/a/174568/254204
    – pico_prob
    Commented Apr 30, 2021 at 21:28
  • echo "${a[@]:1}"
    – Alex
    Commented Jan 14 at 21:31
62
firstitem=$1
shift;
for item in "$@" ; do
  #process item
done
4
  • 2
    Remember that $0 is generally the script name.
    – Amber
    Commented Apr 23, 2010 at 19:31
  • 4
    +1 for showing a simple example; note that the in "$@" part is implied and can be omitted.
    – mklement0
    Commented Apr 9, 2014 at 4:52
  • 1
    @mklement0 You're saying you can just do "for item; do" ? Commented Apr 21, 2014 at 11:57
  • 4
    @DavidDoria: Yes, you can. Try set -- 1 'two' 'three four'; for item; do echo "[$item]"; done
    – mklement0
    Commented Apr 21, 2014 at 13:29
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
5
  • Please also explain the code to be more educative. Commented Apr 9, 2014 at 4: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
    Commented Apr 9, 2014 at 5: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
    Commented Apr 9, 2014 at 5:08
  • @mklement0 I was wonder about where a good place to place a printf to clean the paths ? ( I wish I could be clearer ) Commented Apr 9, 2014 at 6:26
  • Unfortunately, I don't understand. What paths? Clean in what way?
    – mklement0
    Commented Apr 9, 2014 at 13:38

Not the answer you're looking for? Browse other questions tagged or ask your own question.