como acessar o próximo argumento de parâmetros de linha de comando no bash?

3

Digamos que eu tenha o seguinte:

for i in $@; do
    echo ${i+1}
done

e eu executo isso no shell $ test.sh 3 5 8 4 , ele gera 1 1 1 1 por que não funcionaria o $ {i + 1}? Eu estou tentando acessar o próximo argumento para uma lista de argumentos de linha de comando.

    
por 夢のの夢 10.02.2016 / 06:42

4 respostas

3

Cada caractere no shell pode ter um significado especial.

O código ${i+1} não significa "adicionar 1 a i".
Para descobrir o que isso significa, execute este comando:

LESS=+/'\{parameter\:\+word\}' man bash

E leia:

${parameter:+word}
Use Alternate Value. If parameter is null or unset, nothing is substituted, otherwise the expansion of word is substituted.

E um pouco acima:

Omitting the colon results in a test only for a parameter that is unset.

Como $i tem um valor definido pelo loop for i in $@; , o "Valor Alternativo" é substituído e 1 é impresso.

Se você quiser adicionar 1 ao valor dos argumentos, faça o seguinte:

for    i
do     echo "$((i+1))"
done

Não há necessidade de in "$@" (e acostume-se a citar todas as expansões).

$ ./test.sh 3 5 8 4
4
6
9
5

Próximo argumento.

Mas isso não é "o próximo argumento" também. A questão central é com o seu loop, você está usando o valor de argumentos em um loop, não um índice para os argumentos. Você precisa fazer um loop sobre um índice i dos argumentos, não o valor i de cada argumento. Algo como:

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

Isso imprimirá um índice, como mostra:

$ ./test.sh 3 5 8 4
1
2
3
4

Indirection

Como podemos acessar o argumento na posição $i ?: Com indireto:

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

Veja o simples ! adicionado?

Agora funciona assim:

$  ./test.sh  3 5 8 4
3
5
8
4

Solução final.

E para imprimir o argumento atual e o próximo, use isto:

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

Não, não há maneira mais simples do que calcular o valor na variável $j .

$ ./test.sh 3 5 8 4
3 5
5 8
8 4
4 

Isso funciona para o texto também:

$ ./test.sh sa jwe yqs ldfgt
sa jwe
jwe yqs
yqs ldfgt
ldfgt 
    
por 10.02.2016 / 09:15
0

Primeiro, observe que ${i+1} é um padrão de expansão de parâmetro, o que significa que se o parâmetro i não estiver indefinido ou nulo, ele será substituído pela expansão de 1 , o que resulta em 1 . Portanto, você está recebendo todos os 1's na saída.

Você precisa usar um operador aritmético, não expansão de parâmetro.

Por exemplo:

% check () { for i in "$@"; do echo $((i+1)); done ;} 

% check 2 4 5 6
3
5
6
7
    
por 10.02.2016 / 06:46
0

Eu me vejo usando o turno para o mesmo propósito. Veja um pequeno exemplo tirado de aqui :

#!/bin/bash

# This script can clean up files that were last accessed over 365 days ago.

USAGE="Usage: $0 dir1 dir2 dir3 ... dirN"

if [ "$#" == "0" ]; then
    echo "$USAGE"
    exit 1
fi

while (( "$#" )); do

if [[ $(ls "$1") == "" ]]; then 
    echo "Empty directory, nothing to be done."
  else 
    find "$1" -type f -a -atime +365 -exec rm -i {} \;
fi

shift
    
por 10.02.2016 / 07:09
0

No seu código de exemplo, i é o valor, não o índice. Você precisará do ponto de exclamação para usar um valor variável como variável.
Não consegui que o (i + 1) trabalhasse sem definir outra variável. Talvez alguém possa dar uma dica para otimizar isso.

check () {
  for i in $(seq $#); do
    let j=i+1
    echo "$i: i=${!i} i+1=${!j}"
  done
}

check a b c

1: i=a i+1=b
2: i=b i+1=c
3: i=c i+1=
    
por 10.02.2016 / 08:01

Tags