Imprimir argumentos de shell na ordem inversa

12

Estou um pouco preso. Minha tarefa é imprimir os argumentos no meu script em ordem inversa, exceto o terceiro e o quarto.

O que eu tenho é este código:

#!/bin/bash

i=$#
for arg in "$@"
do
    case $i
    in
        3) ;;
        4) ;;
        *) eval echo "$i. Parameter: \$$i";;
    esac
    i=`expr $i - 1`
done

Como odeio eval (saudações ao PHP), estou procurando uma solução sem ela, mas não consigo encontrar uma.

Como posso definir a posição do argumento dinamicamente?

PS: Não, não é uma lição de casa, estou aprendendo a fazer um exame, então tento resolver os exames antigos.

WarrenFaith
fonte

Respostas:

13

evalé a única maneira portátil de acessar um parâmetro posicional por sua posição escolhida dinamicamente. Seu script seria mais claro se você repetisse explicitamente o índice em vez dos valores (que você não está usando). Observe que você não precisa, a exprmenos que queira que seu script seja executado em conchas Bourne antigas; $((…))aritmética está no POSIX. Limite o uso do evalmenor fragmento possível; por exemplo, não use eval echo, atribua o valor a uma variável temporária.

i=$#
while [ "$i" -gt 0 ]; do
  if [ "$i" -ne 3 ] && [ "$i" -ne 2 ]; then
    eval "value=\${$i}"
    echo "Parameter $i is $value"
  fi
  i=$((i-1))
done

No bash, você pode usar ${!i}para significar o valor do parâmetro cujo nome é $i. Isso funciona quando $ié um parâmetro nomeado ou um número (denotando um parâmetro posicional). Enquanto você está nisso, pode usar outros recursos de conveniência do bash.

for ((i=$#; i>0; i--)); do
  if ((i != 3 && i != 4)); then
    echo "Parameter $i is ${!i}"
  fi
done
Gilles 'SO- parar de ser mau'
fonte
Não posso usar, argpois eles estão ordenados corretamente e não ao contrário. Para o uso de expr, estou limitado a usar apenas o padrão.
WarrenFaith 25/09
1
@WarrenFaith Se o seu script começar com #!/bin/bash, você pode usar ${!i}e (()). Se você deseja manter o sh padrão , eles não estão disponíveis, mas $((…))estão.
Gilles 'SO- stop be evil'
Ok, eu acho que eu posso trabalhar com este :)
WarrenFaith
Observe que os antigos reservatórios Bourne não poderiam acessar parâmetros posicionais além de US $ 9.
Stéphane Chazelas
1
evalnão é a única maneira portátil como Ryan mostrou .
Stéphane Chazelas
7

Eu mantenho um script reverseno meu caminho que faz isso:

#!/bin/sh

if [ "$#" -gt 0 ]; then
    arg=$1
    shift
    reverse "$@"
    printf '%s\n' "$arg"
fi

Exemplo de uso:

$ reverse a b c '*' '-n'
-n
*
c
b
a

Você também pode usar uma função em vez de um script dedicado.

Ryan Williams
fonte
Note-se que aquele (ao contrário das outras respostas postadas até agora) também funciona no shell Bourne (não que haja qualquer razão para o uso que a Shell hoje em dia)
Stéphane Chazelas
@ StéphaneChazelas: Por que citar $#? Pode ser algo diferente de um número inteiro não negativo?
G-Man Diz 'Reinstate Monica'
2
@ G-Man, deixar uma variável sem aspas no contexto da lista está chamando o operador split + glob. Não há motivo para você querer invocá-lo aqui. Isso não tem nada a ver com o conteúdo da variável. Consulte também unix.stackexchange.com/a/171347 no final. Observe também que algumas conchas (hífens, por exemplo) ainda herdam o IFS do ambiente.
Stéphane Chazelas
Descobri que, no Android, eu tinha que declararlocal arg=$1
starfry
2

Este é um uso correto e não perigoso de eval. Você controla totalmente o conteúdo que está evalinserindo.

Se isso ainda lhe causar sentimentos ruins, se você não se importa com portabilidade, pode usar a ${!i}sintaxe indireta de Bash .

Shawn J. Goff
fonte
então ${!i}não faz parte da sintaxe padrão?
WarrenFaith 25/09
Não, é um bashismo. Aqui estão as expansões do parâmetro POSIX: pubs.opengroup.org/onlinepubs/009695399/utilities/…
Shawn J. Goff
2

Supondo que os parâmetros posicionais não contenham caracteres de nova linha:

[ "$#" -gt 0 ] && printf '%s\n' "$@" | #print in order
sed '3,4 d' |                          #skip 3 and 4
tac                                    #reverse (try tail -r on
                                       #non-GNU systems).

Teste:

set 1 2 3 4 5 6
printf '%s\n' "$@" | 
sed '3,4 d' |
tac

Saída de teste:

6
5
2
1
PSkocik
fonte
1

Com zsh:

$ set a 'foo bar' c '' '*'
$ printf '<%s>\n' "${(Oa)@}"
<*>
<>
<c>
<foo bar>
<a>

Oaé um sinalizador de expansão de parâmetro para classificar os elementos da matriz após a expansão no sentido inverso índices da matriz .

Para excluir 3 e 4:

$ printf '<%s>\n' "${(Oa)@[5,-1]}" "${(Oa)@[1,2]}"
<*>
<foo bar>
<a>
Stéphane Chazelas
fonte
0

Com basicamente qualquer shell:

printf '{ PS4=\${$(($#-$x))}; } 2>&3; 2>&1\n%.0s' |
x=LINENO+1 sh -sx "$@" 3>/dev/null

E você não precisa usar sub-conchas. Por exemplo:

set -x a b c
{ last= PS4=\${last:=\${$#}}; set +x; } 2>/dev/null
echo "$last"

... imprime ...

c

E aqui está uma função de shell que pode definir um shell aliaspara você que imprimirá os argumentos para frente ou para trás:

tofro() case $1 in (*[!0-9]*|'') ! :;;(*) set "$1"
        until   [ "$1" -eq "$(($#-1))" ]    &&
                shift && alias args=":; printf \
            \"%.\$((\$??\${#*}:0))s%.\$((!\$??\${#*}:0))s\n\" $* "
        do      [ "$#" -gt 1 ] &&
                set "$@ \"\${$#}\" " '"${'"$((1+$1-$#))"'}"' ||
                set "$1" '"$1" "${'"$1"'}"'
        done;   esac

Ele não tenta armazenar os valores literais para nenhum argumento, mas coloca uma string como esta no args alias:

:;printf    "%.$(($??${#*}:0))s%.$((!$??${#*}:0))s\n" \
            "$1" "${3}" "${2}"  "${2}" "${3}"  "${1}"

... e, portanto, armazena apenas referências aos parâmetros para trás e para frente. Ele armazenará até uma contagem, como dado como argumento. E assim o acima aliasfoi gerado como:

tofro 3

printfO comportamento do é afetado com base no valor de retorno do comando anterior - que é sempre :o comando nulo e, portanto, geralmente verdadeiro. printfpulará metade de seus argumentos sempre que imprimir - o que, por padrão, fará com que os argumentos sejam impressos do menor para o maior. No entanto, se você apenas fizer:

! args

... imprime-os ao contrário.

Como o alias não armazena nenhum valor literal, seu valor permanece estático enquanto os argumentos reais podem mudar, mas ainda fará referência a quantos forem. Por exemplo:

set one two three
tofro 3
args; ! args
shift; args; ! args

... que imprime ...

one
two
three
three
two
one
two
three


three
two

Mas a redefinição do alias pode ser feita como:

tofro 2
args; ! args

... e assim imprime ...

two
three
three
two
mikeserv
fonte
-3
declare -a argv=( "$@" )
for (( i=$((${#argv[@]}-1)) ; i>=0 ; i=$(($i-1)) )) ; do
        echo "${argv[$i]}"
        # use "${argv[$i]}" in here...
done
user119141
fonte
2
nenhuma explicação, legal. Voto negativo de mim. Explique o que seu código faz e eu posso mudar de idéia.
WarrenFaith
3
Pode ser simplificado parafor ((i = $# - 1; i >= 0; i--))
Stéphane Chazelas
Estou tendo alucinações? Eu pensei ter visto palavras na pergunta que dizia "imprima os argumentos ... exceto o terceiro e o quarto".
G-Man diz 'Reinstate Monica'
1
@ G-Man, o título diz "imprimir argumentos ao contrário", o que está respondendo sem usar eval. Excluir 3 e 4 disso é trivial. Não acho que os votos negativos sejam justificados. Também é auto-explicativo (embora, como eu disse, possa ser bastante simplificado).
Stéphane Chazelas