Wyświetl argumenty powłoki w odwrotnej kolejności

12

Trochę utknąłem. Moim zadaniem jest wydrukowanie argumentów na skrypcie w odwrotnej kolejności, z wyjątkiem trzeciej i czwartej.

Mam ten kod:

#!/bin/bash

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

Ponieważ nienawidzę eval (pozdrowienia dla PHP), szukam rozwiązania bez niego, ale nie jestem w stanie go znaleźć.

Jak mogę dynamicznie zdefiniować pozycję argumentu?

PS: Nie, to nie praca domowa, uczę się muszli do egzaminu, więc staram się rozwiązywać stare egzaminy.

WarrenFaith
źródło

Odpowiedzi:

13

evaljest jedynym przenośnym sposobem na uzyskanie dostępu do parametru pozycyjnego poprzez jego dynamicznie wybraną pozycję. Skrypt byłby bardziej przejrzysty, gdybyś wyraźnie zapętlił indeks zamiast wartości (których nie używasz). Zauważ, że nie potrzebujesz, exprchyba że chcesz, aby twój skrypt działał w antycznych powłokach Bourne'a; $((…))arytmetyka jest w POSIX. Ogranicz użycie evaldo najmniejszego możliwego fragmentu; na przykład nie używaj eval echo, przypisz wartość do zmiennej tymczasowej.

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

W bash można użyć ${!i}wartości parametru, którego nazwa to $i. Działa $ito, gdy jest albo nazwanym parametrem, albo liczbą (oznaczającą parametr pozycyjny). Podczas gdy możesz to zrobić, możesz skorzystać z innych funkcji wygody bash.

for ((i=$#; i>0; i--)); do
  if ((i != 3 && i != 4)); then
    echo "Parameter $i is ${!i}"
  fi
done
Gilles „SO- przestań być zły”
źródło
Nie mogę używać, argponieważ są one uporządkowane poprawnie, a nie odwrotnie. Do użycia exprograniczam się tylko do korzystania ze standardu.
WarrenFaith
1
@WarrenFaith Jeśli skrypt zaczyna się od #!/bin/bash, możesz użyć ${!i}i (()). Jeśli chcesz trzymać się standardowego sh, nie są one dostępne, ale $((…))są.
Gilles „SO- przestań być zły”,
Ok, myślę, że mogę z tym pracować :)
WarrenFaith,
Zauważ, że antyczne pociski Bourne'a i tak nie byłyby w stanie uzyskać dostępu do parametrów pozycyjnych przekraczających 9 USD.
Stéphane Chazelas,
1
evalnie jest jedynym przenośnym sposobem, jak pokazał Ryan .
Stéphane Chazelas,
7

Trzymam skrypt reversena mojej ścieżce, który robi to:

#!/bin/sh

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

Przykładowe użycie:

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

Możesz także użyć funkcji zamiast dedykowanego skryptu.

Ryan Williams
źródło
Zauważ, że ta (w przeciwieństwie do innych dotychczas opublikowanych odpowiedzi) również działałaby w powłoce Bourne'a (nie dlatego, że obecnie nie ma powodu, aby używać tej powłoki)
Stéphane Chazelas
@ StéphaneChazelas: Dlaczego cytować $#? Czy może to być coś innego niż nieujemna liczba całkowita?
G-Man mówi „Reinstate Monica”
2
@ G-Man, pozostawiając zmienną niecytowaną w kontekście listy, wywołuje operator split + glob. Nie ma powodu, dla którego chciałbyś ją tutaj wywołać. To nie ma nic wspólnego z zawartością zmiennej. Zobacz także unix.stackexchange.com/a/171347 pod koniec. Zauważ też, że niektóre powłoki (na przykład kreska, szyk) nadal dziedziczą IFS ze środowiska.
Stéphane Chazelas,
Odkryłem, że na Androida musiałem zadeklarowaćlocal arg=$1
starfry
2

Jest to prawidłowe i niegroźne użycie eval. Masz pełną kontrolę nad tworzoną treścią eval.

Jeśli nadal sprawia ci to złe przeczucia, to jeśli nie zależy ci na przenośności, możesz użyć ${!i}składni pośredniej Basha .

Shawn J. Goff
źródło
więc ${!i}nie jest częścią standardowej składni?
WarrenFaith
Nie, to bashizm. Oto rozszerzenia parametrów POSIX: pubs.opengroup.org/onlinepubs/009695399/utilities/…
Shawn J. Goff
2

Zakładając, że parametry pozycyjne nie zawierają znaków nowej linii:

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

Wyjście testowe:

6
5
2
1
PSkocik
źródło
1

Z zsh:

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

Oajest flagą rozwinięcia parametru do sortowania elementów tablicy po rozwinięciu w indeksach tablicy odwrotnej .

Aby wykluczyć 3 i 4:

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

Z praktycznie każdą powłoką:

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

I nie musisz używać podpowłoki. Na przykład:

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

... drukuje ...

c

A oto funkcja powłoki, która może ustawić aliasdla ciebie powłokę , która wypisze argumenty do przodu lub do tyłu:

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

Nie próbuje przechowywać wartości literałów dla żadnych argumentów, ale umieszcza taki ciąg w args alias:

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

... i dlatego przechowuje tylko odniesienia do parametrów wstecz i do przodu. Będzie przechowywać do liczby podanej jako argument. Tak więc powyższe aliaszostało wygenerowane jak:

tofro 3

printfNa zachowanie ma wpływ wartość zwracana z poprzedniego polecenia - które zawsze jest :poleceniem zerowym, a więc zwykle prawdziwe. printfpominie połowę swoich argumentów przy każdym drukowaniu - co domyślnie spowoduje wydrukowanie argumentów od najmniejszej do największej. Jeśli jednak po prostu to zrobisz:

! args

... drukuje je w odwrotnej kolejności.

Ponieważ alias nie przechowuje żadnych literalnych wartości, jego wartość pozostaje statyczna, podczas gdy rzeczywiste argumenty mogą się zmieniać, ale nadal będzie odwoływać się do tylu, ile może. Na przykład:

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

... które drukuje ...

one
two
three
three
two
one
two
three


three
two

Ale resetowanie aliasu można wykonać w następujący sposób:

tofro 2
args; ! args

... i tak drukuje ...

two
three
three
two
mikeserv
źródło
-3
declare -a argv=( "$@" )
for (( i=$((${#argv[@]}-1)) ; i>=0 ; i=$(($i-1)) )) ; do
        echo "${argv[$i]}"
        # use "${argv[$i]}" in here...
done
użytkownik119141
źródło
2
nie ma żadnego wyjaśnienia, miło. Głosuj ode mnie. Wyjaśnij, co robi Twój kod, a ja mogę zmienić zdanie.
WarrenFaith
3
Można uprościćfor ((i = $# - 1; i >= 0; i--))
Stéphane Chazelas,
Czy mam halucynacje? Wydawało mi się, że widziałem w pytaniu słowa „wydrukuj argumenty ... oprócz trzeciego i czwartego”.
G-Man mówi „Reinstate Monica”
1
@ G-Man, tytuł mówi „wypisz argumenty w odwrotnej kolejności”, na co odpowiada odpowiedź bez użycia polecenia eval. Wyłączenie 3 i 4 z tego jest banalne. Nie sądzę, by opinie były uzasadnione. Jest to również oczywiste (choć, jak powiedziałem, można by znacznie uprościć).
Stéphane Chazelas,