Stampa gli argomenti della shell in ordine inverso


12

Sono un po 'bloccato. Il mio compito è stampare gli argomenti sul mio script in ordine inverso, tranne il terzo e il quarto.

Quello che ho è questo codice:

#!/bin/bash

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

Dato che odio eval (saluti a PHP), sto cercando una soluzione senza di essa ma non riesco a trovarne una.

Come posso definire dinamicamente la posizione dell'argomento?

PS: No, non è un compito, sto imparando la shell per un esame, quindi provo a risolvere vecchi esami.

Risposte:


13

evalè l'unico modo portatile per accedere a un parametro posizionale dalla sua posizione scelta dinamicamente. Il tuo script sarebbe più chiaro se eseguissi un ciclo esplicito sull'indice anziché sui valori (che non stai utilizzando). Si noti che non è necessario a exprmeno che non si desideri eseguire lo script in antiche shell Bourne; $((…))l'aritmetica è in POSIX. Limitare l'uso del evalpiù piccolo frammento possibile; ad esempio, non utilizzare eval echo, assegnare il valore a una variabile temporanea.

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

In bash, puoi usare ${!i}per indicare il valore del parametro il cui nome è $i. Funziona quando si $itratta di un parametro denominato o di un numero (che indica un parametro posizionale). Mentre ci sei, puoi fare uso di altre funzionalità di convenienza bash.

for ((i=$#; i>0; i--)); do
  if ((i != 3 && i != 4)); then
    echo "Parameter $i is ${!i}"
  fi
done

Non posso usarlo argperché sono ordinati correttamente e non al contrario. Per l'uso di expr, sono limitato a utilizzare solo lo standard.
WarrenFaith,

1
@WarrenFaith Se lo script inizia con #!/bin/bash, puoi usare ${!i}e (()). Se vuoi attenersi allo sh standard , questi non sono disponibili, ma lo $((…))sono.
Gilles 'SO- smetti di essere malvagio' il

Ok, penso di poter lavorare con questo :)
WarrenFaith,

Si noti che le antiche shell Bourne non sarebbero comunque in grado di accedere a parametri posizionali oltre $ 9.
Stéphane Chazelas,

1
evalnon è l'unico modo portatile come ha mostrato Ryan .
Stéphane Chazelas,

7

Tengo uno script reversesul mio percorso che fa questo:

#!/bin/sh

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

Esempio di utilizzo:

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

È inoltre possibile utilizzare una funzione anziché uno script dedicato.


Si noti che uno (contrariamente alle altre risposte postate finora) funzionerebbe anche nella shell Bourne (non che ci sia alcun motivo per utilizzare quella shell al giorno d'oggi)
Stéphane Chazelas,

@ StéphaneChazelas: Perché citare $#? Può essere diverso da un numero intero non negativo?
G-Man dice "Ripristina Monica" il

2
@ G-Man, lasciando una variabile non quotata nel contesto dell'elenco sta invocando l'operatore split + glob. Non c'è motivo per cui tu voglia invocarlo qui. Ciò non ha nulla a che fare con il contenuto della variabile. Vedi anche unix.stackexchange.com/a/171347 verso la fine. Si noti inoltre che alcune shell (trattino, ad esempio posh) ereditano ancora IFS dall'ambiente.
Stéphane Chazelas,

Ho scoperto che, su Android, dovevo dichiararelocal arg=$1
Starfry il


2

Supponendo che i parametri posizionali non contengano caratteri di nuova riga:

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

Test:

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

Uscita di prova:

6
5
2
1

1

Con zsh:

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

Oaè un flag di espansione dei parametri per ordinare gli elementi dell'array al momento dell'espansione in indici di array inversi .

Per escludere 3 e 4:

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

0

Con praticamente qualsiasi shell:

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

E non è necessario utilizzare i subshells. Per esempio:

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

... stampe ...

c

Ed ecco una funzione di shell che può impostare una shell aliasper te che stamperà gli argomenti in avanti o indietro:

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

Non tenta di memorizzare i valori letterali per nessun argomento, ma inserisce una stringa come questa nel args alias:

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

... e quindi memorizza solo i riferimenti ai parametri avanti e indietro. Memorizzerà fino a un conteggio come indicato come argomento. E così quanto sopra è aliasstato generato come:

tofro 3

printfIl comportamento è influenzato in base al valore restituito dal comando precedente, che è sempre :il comando null, e quindi di solito vero. printfsalterà la metà dei suoi argomenti ogni volta che stampa - che, per impostazione predefinita, otterrà gli argomenti stampati dal più piccolo numerato al più grande. Tuttavia, se lo fai solo:

! args

... li stampa al contrario.

Poiché l'alias non memorizza alcun valore letterale, il suo valore rimane statico mentre gli argomenti reali potrebbero cambiare, ma farà comunque riferimento a quanti ne potrebbe. Per esempio:

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

... che stampa ...

one
two
three
three
two
one
two
three


three
two

Ma il ripristino dell'alias può essere fatto come:

tofro 2
args; ! args

... e così stampa ...

two
three
three
two

-3
declare -a argv=( "$@" )
for (( i=$((${#argv[@]}-1)) ; i>=0 ; i=$(($i-1)) )) ; do
        echo "${argv[$i]}"
        # use "${argv[$i]}" in here...
done

2
nessuna spiegazione, bello. Downvote da me. Spiega cosa fa il tuo codice e potrei cambiare idea.
WarrenFaith,

3
Può essere semplificato afor ((i = $# - 1; i >= 0; i--))
Stéphane Chazelas il

Sto allucinando? Pensavo di aver visto delle parole nella domanda che diceva "stampa gli argomenti ... tranne il terzo e il quarto".
G-Man dice "Reinstate Monica" il

1
@G-Man, il titolo dice "stampa argomenti al contrario" a cui sta rispondendo senza usare eval. Escludere 3 e 4 da ciò è banale. Non credo che i downvotes siano giustificati. È anche autoesplicativo (anche se, come ho detto, potrebbe essere semplificato molto).
Stéphane Chazelas,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.