$ @, exceto o primeiro argumento

36

Eu preciso escrever um script de shell que é executado desta maneira:

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

existe um loop for dentro do script

for i in $@

No entanto, como eu sei, $ @ inclui $ 1 até $ ($ # - 1). Mas, para o meu programa, $ 1 é distintamente diferente de $ 2 $ 3 $ 4 etc. Gostaria de fazer um loop de $ 2 até o final ... Como faço para conseguir isso? Obrigado:)

user40780
fonte

Respostas:

47

Primeiro, observe que $@sem aspas não faz sentido e não deve ser usado. $@deve ser usado apenas entre aspas ( "$@") e em contextos de lista.

for i in "$@" se qualifica como um contexto de lista, mas aqui, para fazer um loop sobre os parâmetros posicionais, a forma canônica, mais portátil e mais simples é:

for i
do something with "$i"
done

Agora, para percorrer os elementos a partir do segundo, a maneira canônica e mais portátil é usar shift:

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

Depois shift, o que costumava ser $1foi removido da lista (mas salvou-lo em $first_arg) eo que costumava ser no $2agora está em $1. Os parâmetros posicionais foram trocados de 1 posição para a esquerda (use shift 2para deslocar 2 ...). Então, basicamente, nosso loop está repetindo o que costumava ser o segundo argumento até o último.

Com bash(e zshe ksh93, mas é isso), uma alternativa é fazer:

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

Mas observe que não é uma shsintaxe padrão, portanto, não deve ser usado em um script que comece com #! /bin/sh -.

Em zshou yash, você também pode:

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

para fazer um loop do terceiro para o terceiro último argumento.

In zsh, $@também é conhecido como $argvarray. Portanto, para exibir elementos do início ou do fim das matrizes, você também pode:

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

( shifttambém pode ser escrito 1=()em zsh)

Em bash, você só pode atribuir aos $@elementos com o setbuiltin, para destacar três elementos no final, isso seria algo como:

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

E para passar do terceiro para o terceiro último:

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

POSIX, para exibir os três últimos elementos "$@", você precisará usar um loop:

n=$(($# - 3))
for arg do
  [ "$n" -gt 0 ] && set -- "$@" "$arg"
  shift
  n=$((n - 1))
done
Stéphane Chazelas
fonte
2
Uma possibilidade alternativa (e feia) de bash: variáveis ​​indiretas:for ((i=2; i<=$#; i++)); do something with "${!i}"; done
glenn jackman
Estou mais familiarizado com esta versão, já que estou mais familiarizado com c ++ :)
user40780
10

Eu acho que você quer o shiftembutido. Renomeia $2para $1, $3para $2etc.

Como isso:

shift
for i in "$@"; do
    echo $i
done
John
fonte
você poderia explicar com mais detalhes como conseguir isso no loop for? Obrigado.
user40780
1
Você não - você o usa antes de entrar no forloop, então apenas repassa $ @ normalmente. Após a shiftchamada, $ @ deve serarg2_1 arg2_2 arg2_3...
John
No entanto, vou ter mais uma pergunta: suponha que eu queira fazer um loop de $ 1 a $ ($ # - 2) (ou seja, arg_1 até arg_2 _ # - 1, exceto arg_2 _ #) ... O que devo fazer?
user40780
2

Sempre há a abordagem do homem das cavernas:

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

Isso deixa $@ intacto (caso você queira usá-lo posteriormente) e simplesmente faz um loop sobre todos os argumentos, mas não processa o primeiro.

Scott
fonte
1

No bash, você também pode escrever esse loop com indexação explícita:

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

Isso itera sobre todos os argumentos do segundo ao último. Se você deseja excluir o último argumento, basta fazer isso

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

e se você quiser apenas usar todos os outros argumentos, escreva-o como

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

A história por trás disso é a versão aritmética do forbuiltin , combinada com a contagem de argumentos$# e a variável indireta${…} .

Uma boa aplicação é que você pode usar isso para decidir, dentro do loop, se uma determinada opção aceita o argumento que segue como um valor. Se isso acontecer, incremente i(por exemplo, gravação : $((++i))) para consumir o próximo valor e pule-o durante a iteração.

MvG
fonte