Imprimir argumentos de shell en orden inverso

12

Estoy un poco atascado. Mi tarea es imprimir los argumentos en mi script en orden inverso, excepto el tercero y el cuarto.

Lo que tengo es 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 odio eval (saludos a PHP), estoy buscando una solución sin ella, pero no puedo encontrarla.

¿Cómo puedo definir la posición del argumento dinámicamente?

PD: No, no es una tarea, estoy aprendiendo shell para un examen, así que trato de resolver los exámenes anteriores.

WarrenFe
fuente

Respuestas:

13

evales la única forma portátil de acceder a un parámetro posicional por su posición elegida dinámicamente. Su secuencia de comandos sería más clara si realizara un bucle explícito en el índice en lugar de los valores (que no está utilizando). Tenga en cuenta que no necesita a exprmenos que desee que su script se ejecute en conchas Bourne antiguas; $((…))La aritmética está en POSIX. Limite el uso de evalal fragmento más pequeño posible; por ejemplo, no use eval echo, asigne el valor a una variable temporal.

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

En bash, puede usar ${!i}para significar el valor del parámetro cuyo nombre es $i. Esto funciona cuando $ies un parámetro con nombre o un número (que denota un parámetro posicional). Mientras lo hace, puede hacer uso de otras características convenientes de bash.

for ((i=$#; i>0; i--)); do
  if ((i != 3 && i != 4)); then
    echo "Parameter $i is ${!i}"
  fi
done
Gilles 'SO- deja de ser malvado'
fuente
No puedo usarlos argya que están ordenados correctamente y no a la inversa. Para el uso de expr, estoy limitado a usar solo el estándar.
WarrenFaith
1
@WarrenFaith Si su script comienza con #!/bin/bash, puede usar ${!i}y (()). Si desea seguir el estándar sh, estos no están disponibles, pero lo $((…))están.
Gilles 'SO- deja de ser malvado'
Ok, creo que puedo trabajar con esto :)
WarrenFaith
Tenga en cuenta que las antiguas conchas Bourne no podrían acceder a los parámetros posicionales más allá de $ 9 de todos modos.
Stéphane Chazelas
1
evalno es la única forma portátil como lo ha demostrado Ryan .
Stéphane Chazelas
7

Mantengo un script reverseen mi camino que hace esto:

#!/bin/sh

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

Ejemplo de uso:

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

También puede usar una función en lugar de un script dedicado.

Ryan Williams
fuente
Tenga en cuenta que uno (al contrario de las otras respuestas publicadas hasta ahora) también funcionaría en el shell Bourne (no es que haya ninguna razón para usar ese shell hoy en día)
Stéphane Chazelas
@ StéphaneChazelas: ¿Por qué citar $#? ¿Puede ser algo más que un entero no negativo?
G-Man dice 'Reincorporar a Monica' el
2
@ G-Man, dejar una variable sin comillas en el contexto de la lista invoca al operador split + glob. No hay razón por la que quieras invocarlo aquí. Eso no tiene nada que ver con el contenido de la variable. Consulte también unix.stackexchange.com/a/171347 hacia el final. También tenga en cuenta que algunos shells (guiones, elegantes, por ejemplo) todavía heredan IFS del entorno.
Stéphane Chazelas
Descubrí que, en Android, tenía que declararlocal arg=$1
starfry
2

Este es un uso correcto y no peligroso de eval. Usted controla completamente el contenido que está evaling.

Si todavía te da malos sentimientos, entonces si no te importa la portabilidad, puedes usar la ${!i}sintaxis de indirección de Bash .

Shawn J. Goff
fuente
Entonces, ${!i}¿no es parte de la sintaxis estándar?
WarrenFaith
No, es un bashismo. Aquí están las expansiones de parámetros POSIX: pubs.opengroup.org/onlinepubs/009695399/utilities/…
Shawn J. Goff
2

Suponiendo que los parámetros posicionales no contienen caracteres de nueva línea:

[ "$#" -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).

Prueba:

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

Prueba de salida:

6
5
2
1
PSkocik
fuente
1

Con zsh:

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

Oaes un indicador de expansión de parámetros para ordenar los elementos de la matriz tras la expansión en índices de matriz inversa .

Para excluir 3 y 4:

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

Con básicamente cualquier caparazón:

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

Y no necesitas usar subshell. Por ejemplo:

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

...huellas dactilares...

c

Y aquí hay una función de shell que puede establecer un shell aliaspara usted que imprimirá los argumentos hacia adelante o hacia atrá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

No intenta almacenar los valores literales para ningún argumento, sino que coloca una cadena como esta en args alias:

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

... y almacena solo referencias a los parámetros hacia atrás y hacia adelante. Almacenará hasta un recuento dado como argumento. Y así, lo anterior aliasse generó como:

tofro 3

printfEl comportamiento de 's se ve afectado en función del valor de retorno del comando anterior, que siempre es :el comando nulo y, por lo tanto, generalmente es verdadero. printfomitirá la mitad de sus argumentos cada vez que imprima, lo que, de forma predeterminada, imprimirá los argumentos de menor a mayor número. Sin embargo, si solo haces:

! args

... los imprime al revés.

Debido a que el alias no almacena ningún valor literal, su valor permanece estático mientras que los argumentos reales pueden cambiar, pero aún hará referencia a tantos como podría. Por ejemplo:

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

... que imprime ...

one
two
three
three
two
one
two
three


three
two

Pero restablecer el alias se puede hacer así:

tofro 2
args; ! args

... y así se imprime ...

two
three
three
two
mikeserv
fuente
-3
declare -a argv=( "$@" )
for (( i=$((${#argv[@]}-1)) ; i>=0 ; i=$(($i-1)) )) ; do
        echo "${argv[$i]}"
        # use "${argv[$i]}" in here...
done
usuario119141
fuente
2
No hay explicación en absoluto, agradable. Voto negativo de mi parte. Explica qué hace tu código y podría cambiar de opinión.
WarrenFaith
3
Se puede simplificar afor ((i = $# - 1; i >= 0; i--))
Stéphane Chazelas
¿Estoy alucinando? Me pareció ver palabras en la pregunta que decía "imprime los argumentos ... excepto el tercero y el cuarto".
G-Man dice 'reinstalar a Monica' el
1
@ G-Man, el título dice "imprimir argumentos en reversa" que esto responde sin usar eval. Excluir 3 y 4 de eso es trivial. No creo que los votos negativos estén justificados. También se explica por sí mismo (aunque, como dije, podría simplificarse mucho).
Stéphane Chazelas