Bash – Using $@ Except the First Argument

bashscriptingshell

I need to write a shell script that runs in this way:

./myscript arg1 arg2_1 arg2_2 arg2_3 ....... arg2_#

there is a for loop inside script

for i in $@

However, as I know, $@ includes $1 up to $($#-1). But for my program $1 is distinctly different from $2 $3 $4 etc. I would like to loop from $2 to the end… How do i achieve this? Thank you:)

Best Answer

First, note that $@ without quotes makes no sense and should not be used. $@ should only be used quoted ("$@") and in list contexts.

for i in "$@" qualifies as a list context, but here, to loop over the positional parameters, the canonical, most portable and simpler form is:

for i
do something with "$i"
done

Now, to loop over the elements starting from the second one, the canonical and most portable way is to use shift:

first_arg=$1
shift # short for shift 1
for i
do something with "$i"
done

After shift, what used to be $1 has been removed from the list (but we've saved it in $first_arg) and what used to be in $2 is now in $1. The positional parameters have been shifted 1 position to the left (use shift 2 to shift by 2...). So basically, our loop is looping from what used to be the second argument to the last.

With bash (and zsh and ksh93, but that's it), an alternative is to do:

for i in "${@:2}"
do something with "$i"
done

But note that it's not standard sh syntax so should not be used in a script that starts with #! /bin/sh -.

In zsh or yash, you can also do:

for i in "${@[3,-3]}"
do something with "$i"
done

to loop from the 3rd to the 3rd last argument.

In zsh, $@ is also known as the $argv array. So to pop elements from the beginning or end of the arrays, you can also do:

argv[1,3]=() # remove the first 3 elements
argv[-3,-1]=()

(shift can also be written 1=() in zsh)

In bash, you can only assign to the $@ elements with the set builtin, so to pop 3 elements off the end, that would be something like:

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

And to loop from the 3rd to the 3rd last:

for i in "${@:3:$#-5}"
do something with "$i"
done

POSIXly, to pop the last 3 elements of "$@", you'd need to use a loop:

n=$(($# - 3))
for arg do
  [ "$n" -gt 0 ] && set -- "$@" "$arg"
  shift
  n=$((n - 1))
done
Related Question