Shell-Argumente in umgekehrter Reihenfolge ausgeben

12

Ich stecke ein bisschen fest. Meine Aufgabe ist es, die Argumente für mein Skript in umgekehrter Reihenfolge zu drucken, mit Ausnahme der dritten und vierten.

Was ich habe, ist dieser Code:

#!/bin/bash

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

Da ich eval hasse (Grüße an PHP), suche ich nach einer Lösung ohne diese, kann aber keine finden.

Wie kann ich die Position des Arguments dynamisch definieren?

PS: Nein, es ist keine Hausaufgabe, ich lerne gerade die Shell für eine Prüfung, also versuche ich, alte Prüfungen zu lösen.

WarrenFaith
quelle

Antworten:

13

evalist die einzige tragbare Möglichkeit, über die dynamisch ausgewählte Position auf einen Positionsparameter zuzugreifen. Ihr Skript wäre klarer, wenn Sie explizit den Index und nicht die Werte (die Sie nicht verwenden) durchlaufen würden. Beachten Sie, dass Sie dies nur benötigen, exprwenn Ihr Skript in antiken Bourne-Shells ausgeführt werden soll. $((…))Arithmetik ist in POSIX. Beschränken Sie die Verwendung evalauf das kleinstmögliche Fragment. Verwenden Sie beispielsweise nicht, sondern eval echoweisen Sie den Wert einer temporären Variablen zu.

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 können Sie ${!i}den Wert des Parameters angeben, dessen Name lautet $i. Dies funktioniert, wenn $ies sich entweder um einen benannten Parameter oder eine Zahl handelt (was einen Positionsparameter bezeichnet). Während Sie gerade dabei sind, können Sie andere Bash-Komfortfunktionen nutzen.

for ((i=$#; i>0; i--)); do
  if ((i != 3 && i != 4)); then
    echo "Parameter $i is ${!i}"
  fi
done
Gilles 'SO - hör auf böse zu sein'
quelle
Ich kann sie nicht verwenden, argda sie korrekt und nicht umgekehrt bestellt sind. Für die Verwendung von exprbin ich nur auf die Verwendung des Standards beschränkt.
WarrenFaith
1
@WarrenFaith Wenn Ihr Skript mit beginnt #!/bin/bash, können Sie ${!i}und verwenden (()). Wenn Sie sich an Standard- sh halten möchten , sind diese nicht verfügbar, sind es aber $((…)).
Gilles 'SO- hör auf böse zu sein'
Ok, ich denke, ich kann damit arbeiten :)
WarrenFaith
Beachten Sie, dass die antiken Bourne-Shells ohnehin nicht über 9 USD hinaus auf Positionsparameter zugreifen können.
Stéphane Chazelas
1
evalist nicht der einzige tragbare Weg, wie Ryan gezeigt hat .
Stéphane Chazelas
7

Ich behalte ein Skript reverseauf meinem Weg, das dies tut:

#!/bin/sh

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

Beispielverwendung:

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

Sie können auch eine Funktion anstelle eines dedizierten Skripts verwenden.

Ryan Williams
quelle
Beachten Sie, dass eine (im Gegensatz zu den anderen bisher veröffentlichten Antworten) auch in der Bourne-Shell funktioniert (nicht, dass es heutzutage einen Grund gibt, diese Shell zu verwenden)
Stéphane Chazelas
@ StéphaneChazelas: Warum zitieren $#? Kann es etwas anderes als eine nicht negative ganze Zahl sein?
G-Man sagt, dass Monica am
2
@ G-Man: Wenn eine Variable im Listenkontext nicht in Anführungszeichen gesetzt wird, wird der Operator split + glob aufgerufen. Es gibt keinen Grund, warum Sie es hier aufrufen möchten. Das hat nichts mit dem Inhalt der Variablen zu tun. Siehe auch unix.stackexchange.com/a/171347 gegen Ende. Beachten Sie auch, dass einige Shells (z. B. Dash, Posh) immer noch IFS von der Umgebung erben.
Stéphane Chazelas
Ich fand heraus, dass ich unter Androidlocal arg=$1
starfry
2

Dies ist eine korrekte und ungefährliche Verwendung von eval. Sie haben die volle Kontrolle über den Inhalt, den Sie gerade haben eval.

Wenn Sie immer noch schlechte Gefühle verspüren, können Sie die ${!i}Indirektionssyntax von Bash verwenden, wenn Sie sich nicht für Portabilität interessieren .

Shawn J. Goff
quelle
Ist ${!i}das nicht Teil der Standardsyntax?
WarrenFaith
Nein, es ist ein Bashismus. Hier sind die POSIX-Parametererweiterungen: pubs.opengroup.org/onlinepubs/009695399/utilities/…
Shawn J. Goff
2

Vorausgesetzt, die Positionsparameter enthalten keine Zeilenumbrüche:

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

Prüfung:

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

Testausgang:

6
5
2
1
PSkocik
quelle
1

Mit zsh:

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

Oaist ein Parametererweiterungsflag zum Sortieren der Arrayelemente bei der Erweiterung in umgekehrte Arrayindizes.

3 und 4 ausschließen:

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

Mit grundsätzlich jeder Shell:

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

Und Sie nicht brauchen , verwenden Subshells. Beispielsweise:

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

... druckt ...

c

Und hier ist eine Shell-Funktion, mit der Sie eine Shell festlegen können alias, die die Argumente vorwärts oder rückwärts ausgibt:

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

Es wird nicht versucht, die Literalwerte für Argumente zu speichern, sondern es wird eine Zeichenfolge wie die folgende in das args aliasfolgende Feld eingefügt :

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

... und speichert so nur Verweise auf die Parameter vor und zurück. Es wird bis zu einer Anzahl gespeichert, die als Argument angegeben wird. Und so wurde das oben aliasGenannte wie folgt generiert:

tofro 3

printfDas Verhalten von wird basierend auf dem Rückgabewert des vorherigen Befehls beeinflusst - der immer :der Null-Befehl ist und daher normalerweise wahr ist. printfBei jedem Ausdruck wird die Hälfte der Argumente übersprungen. Standardmäßig werden die Argumente von klein nach groß gedruckt. Wenn Sie jedoch nur Folgendes tun:

! args

... es druckt sie umgekehrt.

Da der Alias ​​keine Literalwerte speichert, bleibt sein Wert statisch, während sich die tatsächlichen Argumente ändern, er verweist jedoch weiterhin auf so viele wie möglich. Beispielsweise:

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

... was druckt ...

one
two
three
three
two
one
two
three


three
two

Das Zurücksetzen des Alias ​​kann folgendermaßen erfolgen:

tofro 2
args; ! args

... und so druckt es ...

two
three
three
two
mikeserv
quelle
-3
declare -a argv=( "$@" )
for (( i=$((${#argv[@]}-1)) ; i>=0 ; i=$(($i-1)) )) ; do
        echo "${argv[$i]}"
        # use "${argv[$i]}" in here...
done
user119141
quelle
2
überhaupt keine Erklärung, nett. Ablehnung von mir. Erklären Sie, was Ihr Code tut und ich könnte meine Meinung ändern.
WarrenFaith
3
Kann vereinfacht werdenfor ((i = $# - 1; i >= 0; i--))
Stéphane Chazelas
Halluziniere ich? Ich dachte, ich hätte Wörter in der Frage gesehen, die besagten: "Drucke die Argumente ... außer dem dritten und vierten".
G-Man sagt, dass Monica am
1
@ G-Man, der Titel sagt "Argumente in umgekehrter Reihenfolge drucken", was dies beantwortet, ohne eval zu verwenden. Das Ausschließen von 3 und 4 ist trivial. Ich denke nicht, dass die Ablehnungen gerechtfertigt sind. Es ist auch selbsterklärend (obwohl, wie gesagt, sehr vereinfacht werden könnte).
Stéphane Chazelas