Imprime os argumentos do shell na ordem inversa

12

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

O que eu tenho é esse 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 eu odeio o eval (saudações ao PHP), estou procurando uma solução sem ele, mas não consigo encontrar um.

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

PS: Não, não é uma lição de casa, eu estou aprendendo shell para um exame, então eu tento resolver exames antigos.

    
por WarrenFaith 25.09.2011 / 17:28

7 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ê explicitamente colocasse em loop o índice em vez dos valores (que você não está usando). Note que você não precisa de expr a menos que você queira que seu script seja executado em shells antigos da Bourne; $((…)) aritmética está em POSIX. Limite o uso de eval ao menor 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, você pode fazer uso de outros recursos convenientes bash.

for ((i=$#; i>0; i--)); do
  if ((i != 3 && i != 4)); then
    echo "Parameter $i is ${!i}"
  fi
done
    
por 25.09.2011 / 18:02
7

Eu mantenho um script reverse no 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.

    
por 06.05.2013 / 21:55
2

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

Se ainda assim você tiver sentimentos ruins, se não se importar com a portabilidade, use a sintaxe ${!i} indireta do Bash.

    
por 25.09.2011 / 18:01
2

Supondo que os parâmetros posicionais não contêm 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

Teste de saída:

6
5
2
1
    
por 12.06.2015 / 14:56
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 em índices de matriz reversos .

Para excluir 3 e 4:

$ printf '<%s>\n' "${(Oa)@[5,-1]}" "${(Oa)@[1,2]}"
<*>
<foo bar>
<a>
    
por 12.06.2015 / 13:32
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 para usar subshells. 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 do shell que pode definir um shell alias para 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, em vez disso, coloca uma string como essa em args alias :

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

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

tofro 3
O comportamento de

printf é afetado com base no valor de retorno do comando anterior - que é sempre : do comando nulo e, portanto, é geralmente verdadeiro. printf irá omitir metade de seus argumentos toda vez que for impresso - o que, por padrão, obterá os argumentos impressos do menor para o maior. No entanto, se você acabou de fazer:

! args

... imprime-os ao contrário.

Como o alias não armazena valores literais, seu valor permanece estático enquanto os argumentos reais podem ser alterados, mas ele ainda fará referência a quantos puder. 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
    
por 12.06.2015 / 14:47
-3
declare -a argv=( "$@" )
for (( i=$((${#argv[@]}-1)) ; i>=0 ; i=$(($i-1)) )) ; do
        echo "${argv[$i]}"
        # use "${argv[$i]}" in here...
done
    
por 12.06.2015 / 13:18