$ @ sauf le 1er argument

36

J'ai besoin d'écrire un script shell qui fonctionne de cette façon:

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

il y a une boucle for dans le script

for i in $@

Cependant, comme je le sais, $ @ comprend 1 $ jusqu'à $ ($ - 1). Mais pour mon programme, $ 1 diffère nettement de $ 2 $ 3 $ 4 $, etc. J'aimerais passer de 2 $ à la fin ... Comment y parvenir? Merci:)

utilisateur40780
la source

Réponses:

47

Tout d'abord, notez que $@sans guillemets n'a pas de sens et ne doit pas être utilisé. $@doit être utilisé uniquement entre guillemets ( "$@") et dans des contextes de liste.

for i in "$@" qualifie comme un contexte de liste, mais ici, pour boucler sur les paramètres de position, la forme canonique, la plus portable et la plus simple est:

for i
do something with "$i"
done

Maintenant, pour boucler sur les éléments à partir du second, la manière la plus canonique et la plus portable est d’utiliser shift:

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

Après shift, ce qui était auparavant $1a été supprimé de la liste (mais nous l'avons enregistré $first_arg) et ce qui était auparavant $2est maintenant $1. Les paramètres de position ont été décalés 1 à gauche (utilisez shift 2pour décaler de 2 ...). Donc, fondamentalement, notre boucle boucle de ce qui était le deuxième argument au dernier.

Avec bash(et zshet ksh93, mais c'est tout), une alternative est de faire:

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

Mais notez que ce n'est pas une shsyntaxe standard et qu'il ne faut donc pas l'utiliser dans un script qui commence par #! /bin/sh -.

En zshou yash, vous pouvez aussi faire:

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

faire une boucle du 3ème au 3ème dernier argument.

Dans zsh, $@est également connu sous le nom de $argvtableau. Donc, pour faire apparaître des éléments au début ou à la fin des tableaux, vous pouvez également faire:

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

( shiftpeut également être écrit 1=()dans zsh)

Dans bash, vous ne pouvez affecter que les $@éléments avec la fonction setintégrée. Par conséquent, pour extraire 3 éléments à la fin, cela ressemblerait à quelque chose comme:

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

Et pour boucler du 3 au 3 dernier:

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

POSIXly, pour faire apparaître les 3 derniers éléments de "$@", vous devez utiliser une boucle:

n=$(($# - 3))
for arg do
  [ "$n" -gt 0 ] && set -- "$@" "$arg"
  shift
  n=$((n - 1))
done
Stéphane Chazelas
la source
2
Une possibilité bash alternative (et laide): variables indirectes:for ((i=2; i<=$#; i++)); do something with "${!i}"; done
glenn jackman
Je suis plus familier avec cette version, puisque je suis plus familier avec c ++ :)
user40780 Le
10

Je pense que vous voulez la fonction shiftintégrée. Il renomme $2à $1, $3à $2, etc.

Comme ça:

shift
for i in "$@"; do
    echo $i
done
John
la source
Pourriez-vous expliquer plus en détail comment y parvenir dans la boucle? Merci.
user40780
1
Vous ne l'utilisez pas - vous l'utilisez avant d'entrer dans la forboucle, puis vous parcourez simplement $ @ normalement. Après l' shiftappel, $ @ devrait êtrearg2_1 arg2_2 arg2_3...
John
Cependant, j'aurai une autre question: supposons que je veuille boucler de $ 1 à $ ($ # - 2) (ie arg_1 à arg_2 _ # - 1, sauf arg_2 _ #) ... Que dois-je faire?
user40780
2

Il y a toujours l'approche homme des cavernes:

first=1
for i
do
        if [ "$first" ]
        then
                first=
                continue
        fi
        something with "$i"
done

Cela laisse $@intact (au cas où vous voudriez l'utiliser plus tard), et boucle simplement sur chaque argument, mais ne traite pas le premier.

Scott
la source
1

En bash, vous pouvez également écrire cette boucle avec une indexation explicite:

for ((i=2; i<=$#; ++i)); do
  process "${!i}"
done

Ceci itère sur tous les arguments du deuxième au dernier. Si vous voulez exclure le dernier argument à la place, faites simplement ceci

for ((i=1; i<=$#-1; ++i)); do
  process "${!i}"
done

et si vous voulez seulement prendre tous les autres arguments, écrivez-le comme

for ((i=1; i<=$#; i+=2)); do
  process "${!i}"
done

L'histoire derrière ceci est la version arithmétique de la commande forintégrée , combinée au nombre d' arguments$# et à l' indirection de variable${…} .

Une bonne application est que vous pouvez utiliser ceci pour décider, dans la boucle, si une option donnée prendra l'argument qui le suit comme valeur. Si c'est le cas, incrémentez i(par exemple en écrivant : $((++i))) pour utiliser la valeur suivante et ignorez-la lors de l'itération.

MvG
la source