Das letzte Argument an ein Shell-Skript übergeben

272

$1ist das erste Argument.
$@ist alle von ihnen.

Wie finde ich das letzte Argument, das an ein Shell-Skript übergeben wurde?

Thomas
quelle
Ich habe Bash verwendet, aber je tragbarer die Lösung, desto besser.
Thomas
10
Verwendung kann auch $ {! #}
Prateek Joshi
11
Denn nur bash , die Kevin Kleine Antwort schlägt vor , die einfach ${!#}. Testen Sie es mit bash -c 'echo ${!#}' arg1 arg2 arg3. Für bash , ksh und zsh , die Dennis Williamson Antwort schlägt ${@: -1}. Darüber hinaus ${*: -1}kann auch verwendet werden. Testen Sie es mit zsh -c 'echo ${*: -1}' arg1 arg2 arg3. Dies funktioniert jedoch nicht für dash , csh und tcsh .
Olibre
2
${!#}${@: -1}funktioniert im Gegensatz dazu auch mit Parametererweiterung. Sie können es mit testen bash -c 'echo ${!#%.*}' arg1.out arg2.out arg3.out.
Arch Stanton

Antworten:

177

Das ist ein bisschen wie ein Hack:

for last; do true; done
echo $last

Dieser ist auch ziemlich portabel (sollte wieder mit bash, ksh und sh funktionieren) und verschiebt die Argumente nicht, was nett sein könnte.

Es wird die Tatsache verwendet, dass fordie Argumente implizit durchlaufen werden, wenn Sie nicht angeben, was durchlaufen werden soll, und die Tatsache, dass for-Schleifenvariablen keinen Gültigkeitsbereich haben: Sie behalten den letzten Wert bei, auf den sie festgelegt wurden.

Laurence Gonsalves
quelle
10
@ MichałŠrajer, ich denke du meintest Doppelpunkt und nicht Komma;)
Paweł Nadolski
2
@ MichałŠrajer trueist Teil von POSIX .
Rufflewind
4
Mit einem alten Solaris, mit der alten Bourne-Shell (nicht POSIX), muss ich "zum
Schluss
8
@mcoolive @LaurenceGolsalves ist nicht nur portabler, sondern for last in "$@"; do :; donemacht auch die Absicht viel klarer.
Adrian Günter
1
@mcoolive: Es funktioniert sogar in der Unix v7 bourne Shell von 1979. Sie können nicht portabler werden, wenn es sowohl auf v7 sh als auch auf POSIX sh läuft :)
Dominik R.
291

Dies ist nur Bash:

echo "${@: -1}"
Bis auf weiteres angehalten.
quelle
43
Für diejenigen (wie ich), die sich fragen, warum der Platz benötigt wird, hat man bash folgendes zu sagen:> Beachten Sie, dass ein negativer Versatz durch mindestens ein Leerzeichen vom Doppelpunkt getrennt sein muss, um nicht mit der folgenden Erweiterung verwechselt zu werden: -.
foo
1
Ich habe dies verwendet und es bricht in MSYS2 Bash nur in Windows. Bizarr.
Steven Lu
2
Hinweis: Diese Antwort funktioniert für alle Bash-Arrays, anders als ${@:$#}nur für $@. Wenn Sie mit $@in ein neues Array kopieren würden arr=("$@"), ${arr[@]:$#}wäre dies undefiniert. Dies liegt daran, dass $@es ein 0. Element gibt, das nicht in "$@"Erweiterungen enthalten ist.
Mr. Llama
@ Mr.Llama: Ein weiterer Punkt, den Sie vermeiden sollten, $#ist das Iterieren über Arrays, da Arrays in Bash spärlich sind und zwar $#die Anzahl der Elemente im Array anzeigen, aber nicht unbedingt auf das letzte Element (oder Element + 1) zeigen. Mit anderen Worten, man sollte die Indizes nicht tun for ((i = 0; i++; i < $#)); do something "${array[$i]}"; doneund stattdessen for element in "${array[@]}"; do something "$element"; donedurchlaufen oder durchlaufen: for index in "${!array[@]}"; do something "$index" "${array[$index]}"; donewenn Sie etwas mit den Werten der Indizes tun müssen.
Bis auf weiteres angehalten.
80
$ set quick brown fox jumps

$ echo ${*: -1:1} # last argument
jumps

$ echo ${*: -1} # or simply
jumps

$ echo ${*: -2:1} # next to last
fox

Der Speicherplatz ist erforderlich, damit er nicht als Standardwert interpretiert wird .

Beachten Sie, dass dies nur Bash ist.

Steven Penny
quelle
9
Beste Antwort, da es auch das vorletzte Argument enthält. Vielen Dank!
e40
1
Steven, ich weiß nicht, was du getan hast, um in der Strafbank zu landen, aber ich liebe deine Arbeit hier.
Bruno Bronosky
4
Ja. einfach das beste. alle außer Befehl und letztes Argument ${@: 1:$#-1}
Dyno Fu
1
@ DynoFu danke dafür, du hast meine nächste Frage beantwortet. Eine Verschiebung könnte also so aussehen: echo ${@: -1} ${@: 1:$#-1}Wo zuletzt zuerst wird und der Rest nach unten rutscht
Mike
73

Die einfachste Antwort für Bash 3.0 oder höher ist

_last=${!#}       # *indirect reference* to the $# variable
# or
_last=$BASH_ARGV  # official built-in (but takes more typing :)

Das ist es.

$ cat lastarg
#!/bin/bash
# echo the last arg given:
_last=${!#}
echo $_last
_last=$BASH_ARGV
echo $_last
for x; do
   echo $x
done

Ausgabe ist:

$ lastarg 1 2 3 4 "5 6 7"
5 6 7
5 6 7
1
2
3
4
5 6 7
Kevin Little
quelle
1
$BASH_ARGVfunktioniert nicht innerhalb einer Bash-Funktion (es sei denn, ich mache etwas falsch).
Big McLargeHuge
1
Der BASH_ARGV hat die Argumente, als bash aufgerufen wurde (oder eine Funktion), nicht die aktuelle Liste der Positionsargumente.
Isaac
Beachten Sie auch BASH_ARGV, dass Sie den Wert erhalten, der als letztes Argument angegeben wurde, anstatt einfach "den letzten Wert". Zum Beispiel!: Wenn Sie ein einzelnes Argument angeben, dann rufen Sie shift auf, ${@:$#}erzeugen nichts (weil Sie das einzige Argument verschoben haben!), BASH_ARGVGeben Ihnen jedoch immer noch das (früher) letzte Argument.
Steven Lu
30

Verwenden Sie die Indizierung kombiniert mit der Länge von:

echo ${@:${#@}} 

Beachten Sie, dass dies nur Bash ist.

Mark Byers
quelle
24
Kürzere:echo ${@:$#}
Bis auf weiteres angehalten.
1
echo $ (@ & $; & ^ $ &% @ ^! @% ^ # ** # & @ * # @ * # @ (& * # _ ** ^ @ ^ & (^ @ & * & @)
Atul
29

Folgendes wird für Sie funktionieren. Das @ steht für ein Array von Argumenten. : bedeutet bei. $ # ist die Länge des Array von Argumenten. Das Ergebnis ist also das letzte Element:

${@:$#} 

Beispiel:

function afunction{
    echo ${@:$#} 
}
afunction -d -o local 50
#Outputs 50

Beachten Sie, dass dies nur Bash ist.

Tahsin Turkoz
quelle
Während das Beispiel für eine Funktion ist, funktionieren Skripte auf die gleiche Weise. Ich mag diese Antwort, weil sie klar und prägnant ist.
Jonah Braun
1
Und es ist nicht hacky. Es werden explizite Merkmale der Sprache verwendet, keine Nebenwirkungen oder spezielle Qwerks. Dies sollte die akzeptierte Antwort sein.
musicin3d
25

Ich habe dies gefunden, als ich versucht habe, das letzte Argument von allen vorherigen zu trennen. Während einige der Antworten das letzte Argument erhalten, sind sie nicht sehr hilfreich, wenn Sie auch alle anderen Argumente benötigen. Das funktioniert viel besser:

heads=${@:1:$#-1}
tail=${@:$#}

Beachten Sie, dass dies nur Bash ist.

AgileZebra
quelle
3
Die Antwort des Steven Penny ist ein bisschen schöner: Verwendung ${@: -1}für letzte und ${@: -2:1}für vorletzter (und so weiter ...). Beispiel: bash -c 'echo ${@: -1}' prog 0 1 2 3 4 5 6Drucke 6. Um bei diesem aktuellen Ansatz von AgileZebra zu bleiben, verwenden Sie ${@:$#-1:1}, um den vorletzten zu erhalten . Beispiel: bash -c 'echo ${@:$#-1:1}' prog 0 1 2 3 4 5 6Drucke 5. (und ${@:$#-2:1}um den drittletzten zu bekommen und so weiter ...)
Olibre
4
Die Antwort von AgileZebra bietet eine Möglichkeit, alle bis auf die letzten Argumente zu erhalten, sodass ich nicht sagen würde, dass die Antwort von Steven sie ersetzt. Es scheint jedoch keinen Grund zu geben $((...)), die 1 zu subtrahieren. Sie können sie einfach verwenden ${@:1:$# - 1}.
Dkasak
Danke dkasak. Aktualisiert, um Ihre Vereinfachung widerzuspiegeln.
AgileZebra
22

Dies funktioniert in allen POSIX-kompatiblen Shells:

eval last=\${$#}

Quelle: http://www.faqs.org/faqs/unix-faq/faq/part2/section-12.html

Poiuz
quelle
2
Siehe diesen Kommentar im Anhang zu einer älteren identischen Antwort.
Bis auf weiteres angehalten.
1
Die einfachste tragbare Lösung, die ich hier sehe. Dieser hat kein Sicherheitsproblem, @DennisWilliamson, das empirische Zitieren scheint richtig zu sein, es sei denn, es gibt eine Möglichkeit $#, eine beliebige Zeichenfolge festzulegen (ich glaube nicht). eval last=\"\${$#}\"funktioniert auch und ist offensichtlich richtig. Sehen Sie nicht, warum die Anführungszeichen nicht benötigt werden.
Palec
1
Für bash ist zsh , dash und ksh eval last=\"\${$#}\" in Ordnung. Aber für csh und tcsh verwenden eval set last=\"\${$#}\". Siehe dieses Beispiel : tcsh -c 'eval set last=\"\${$#}\"; echo "$last"' arg1 arg2 arg3.
Olibre
12

Hier ist meine Lösung:

  • ziemlich portabel (alle POSIX sh, bash, ksh, zsh) sollte funktionieren
  • verschiebt nicht die ursprünglichen Argumente (verschiebt eine Kopie).
  • benutzt das Böse nicht eval
  • durchläuft nicht die gesamte Liste
  • verwendet keine externen Tools

Code:

ntharg() {
    shift $1
    printf '%s\n' "$1"
}
LAST_ARG=`ntharg $# "$@"`
Michał Šrajer
quelle
1
Tolle Antwort - kurz, tragbar, sicher. Vielen Dank!
Ján Lalinský
2
Dies ist eine großartige Idee, aber ich habe ein paar Vorschläge: Erstens zitiert sowohl um hinzugefügt werden soll "$1"und "$#"(diese große Antwort unix.stackexchange.com/a/171347 ). Zweitens echoist leider nicht tragbar (insbesondere für -n), printf '%s\n' "$1"sollte also stattdessen verwendet werden.
Joki
danke @joki Ich habe mit vielen verschiedenen Unix-Systemen gearbeitet und würde auch nicht vertrauen echo -n, aber mir ist nicht bekannt, auf welchem ​​Posix-System ein Fehler auftreten echo "$1"würde. Jedenfalls printfist in der Tat vorhersehbarer - aktualisiert.
Michał Šrajer
@ MichałŠrajer betrachten den Fall , in dem „$ 1“ „-n“ oder „-“ ist , so zum Beispiel ntharg 1 -noder ntharg 1 --können unterschiedliche Ergebnisse auf verschiedenen Systemen ergeben. Der Code, den Sie jetzt haben, ist sicher!
Joki
10

Von den ältesten zu den neueren Lösungen:

Die tragbarste Lösung, auch älter sh(funktioniert mit Leerzeichen und Glob-Zeichen) (keine Schleife, schneller):

eval printf "'%s\n'" "\"\${$#}\""

Seit Version 2.01 von bash

$ set -- The quick brown fox jumps over the lazy dog

$ printf '%s\n'     "${!#}     ${@:(-1)} ${@: -1} ${@:~0} ${!#}"
dog     dog dog dog dog

Für ksh, zsh und bash:

$ printf '%s\n' "${@: -1}    ${@:~0}"     # the space beetwen `:`
                                          # and `-1` is a must.
dog   dog

Und für "vorletzte":

$ printf '%s\n' "${@:~1:1}"
lazy

Verwenden von printf, um Probleme mit Argumenten zu umgehen, die mit einem Bindestrich beginnen (z -n ).

Für alle Shells und für ältere sh(funktioniert mit Leerzeichen und Glob-Zeichen) gilt:

$ set -- The quick brown fox jumps over the lazy dog "the * last argument"

$ eval printf "'%s\n'" "\"\${$#}\""
The last * argument

Oder wenn Sie eine Variable festlegen möchten last:

$ eval last=\${$#}; printf '%s\n' "$last"
The last * argument

Und für "vorletzte":

$ eval printf "'%s\n'" "\"\${$(($#-1))}\""
dog
Isaac
quelle
8

Wenn Sie Bash> = 3.0 verwenden

echo ${BASH_ARGV[0]}
dusan
quelle
Auch für das vorletzte Argument, tunecho ${BASH_ARGV[1]}
Steven Penny
1
ARGVsteht für "Argumentvektor".
Serge Stroobandt
5

Für bash, dieser Kommentar vorgeschlagen , die sehr elegant:

echo "${@:$#}"

Als Bonus funktioniert dies auch in zsh.

Tom Hale
quelle
3
shift `expr $# - 1`
echo "$1"

Dies verschiebt die Argumente um die Anzahl der Argumente minus 1 und gibt das erste (und einzige) verbleibende Argument zurück, das das letzte sein wird.

Ich habe nur in Bash getestet, aber es sollte auch in Sh und Ksh funktionieren.

Laurence Gonsalves
quelle
11
shift $(($# - 1))- Keine Notwendigkeit für ein externes Dienstprogramm. Funktioniert in Bash, ksh, zsh und dash.
Bis auf weiteres angehalten.
@ Tennis: Schön! Ich wusste nichts über die $((...))Syntax.
Laurence Gonsalves
Sie möchten verwenden printf '%s\n' "$1", um unerwartetes Verhalten von echo(z -n. B. für ) zu vermeiden .
Joki
2

Wenn Sie dies zerstörungsfrei tun möchten, besteht eine Möglichkeit darin, alle Argumente an eine Funktion zu übergeben und das letzte zurückzugeben:

#!/bin/bash

last() {
        if [[ $# -ne 0 ]] ; then
            shift $(expr $# - 1)
            echo "$1"
        #else
            #do something when no arguments
        fi
}

lastvar=$(last "$@")
echo $lastvar
echo "$@"

pax> ./qq.sh 1 2 3 a b
b
1 2 3 a b

Wenn es dich eigentlich nicht interessiert , die anderen Argumente beizubehalten, brauchen Sie sie nicht in einer Funktion, aber es fällt mir schwer, an eine Situation zu denken, in der Sie die anderen Argumente niemals behalten möchten, es sei denn, sie wurden bereits verarbeitet. In diesem Fall würde ich die Methode process / shift / process / shift / ... verwenden, um sie nacheinander zu verarbeiten.

Ich gehe hier davon aus, dass Sie sie behalten möchten, weil Sie die sequentielle Methode nicht befolgt haben . Diese Methode behandelt auch den Fall, dass keine Argumente vorhanden sind und "" zurückgegeben werden. Sie können dieses Verhalten leicht anpassen, indem Sie die auskommentierte elseKlausel einfügen .

paxdiablo
quelle
2

Für tcsh:

set X = `echo $* | awk -F " " '{print $NF}'`
somecommand "$X"

Ich bin mir ziemlich sicher, dass dies eine tragbare Lösung wäre, abgesehen von der Aufgabe.

Perfect64
quelle
Ich weiß, dass Sie dies für immer gepostet haben, aber diese Lösung ist großartig - ich bin froh, dass jemand eine tcsh gepostet hat!
user3295674
2

Ich fand die Antwort von @ AgileZebra (plus den Kommentar von @ starfry) am nützlichsten, aber sie setzt headsauf einen Skalar. Ein Array ist wahrscheinlich nützlicher:

heads=( "${@:1:$(($# - 1))}" )
tail=${@:${#@}}

Beachten Sie, dass dies nur Bash ist.

EndlosSchleife
quelle
Wie würden Sie die Köpfe in ein Array umwandeln?
Stephane
Ich habe bereits die Klammern hinzugefügt, zum Beispiel echo "${heads[-1]}"das letzte Element in gedruckt heads. Oder fehlt mir etwas?
EndlosSchleife
1

Eine Lösung mit eval:

last=$(eval "echo \$$#")

echo $last
Mikael S.
quelle
7
eval für indirekte Referenz ist übertrieben, nicht schlecht Praxis zu erwähnen, und ein riesiges Sicherheitsproblem (der Wert ist nicht Zitat in Echo oder außerhalb $ () Bash hat einen eingebauten Syntax für indirekte Verweis, für jede var. !So last="${!#}"würde verwendet die gleiche Ansatz (indirekte Referenz auf $ #) in einer viel sichereren, kompakteren, eingebauten, vernünftigen Weise. Und richtig zitiert.
MestreLion
Sehen Sie in einer anderen Antwort einen saubereren Weg, um dasselbe zu tun .
Palec
@ MestreLion-Anführungszeichen werden auf der rechten Seite von nicht benötigt =.
Tom Hale
1
@TomHale: true für den speziellen Fall von ${!#}, aber nicht allgemein: Anführungszeichen werden weiterhin benötigt, wenn der Inhalt wörtliche Leerzeichen enthält, z last='two words'. Nur $()ist Leerzeichen sicher, unabhängig vom Inhalt.
MestreLion
1

Nachdem ich die obigen Antworten gelesen hatte, schrieb ich ein Q & D-Shell-Skript (sollte mit sh und bash funktionieren), um g ++ auf PGM.cpp auszuführen und ein ausführbares Image-PGM zu erstellen. Es wird davon ausgegangen, dass das letzte Argument in der Befehlszeile der Dateiname ist (.cpp ist optional) und alle anderen Argumente Optionen sind.

#!/bin/sh
if [ $# -lt 1 ]
then
    echo "Usage: `basename $0` [opt] pgm runs g++ to compile pgm[.cpp] into pgm"
    exit 2
fi
OPT=
PGM=
# PGM is the last argument, all others are considered options
for F; do OPT="$OPT $PGM"; PGM=$F; done
DIR=`dirname $PGM`
PGM=`basename $PGM .cpp`
# put -o first so it can be overridden by -o specified in OPT
set -x
g++ -o $DIR/$PGM $OPT $DIR/$PGM.cpp
David E.
quelle
1

Folgendes wird auf LASTdas letzte Argument gesetzt, ohne die aktuelle Umgebung zu ändern:

LAST=$({
   shift $(($#-1))
   echo $1
})
echo $LAST

Wenn andere Argumente nicht mehr benötigt werden und verschoben werden können, kann dies vereinfacht werden zu:

shift $(($#-1))
echo $1

Aus Gründen der Portabilität:

shift $(($#-1));

kann ersetzt werden durch:

shift `expr $# - 1`

$()Wenn wir auch durch Backquotes ersetzen, erhalten wir:

LAST=`{
   shift \`expr $# - 1\`
   echo $1
}`
echo $LAST
Paweł Nadolski
quelle
1
echo $argv[$#argv]

Jetzt muss ich nur noch Text hinzufügen, da meine Antwort zu kurz war, um sie zu veröffentlichen. Ich muss mehr Text zum Bearbeiten hinzufügen.

frank1rizzo
quelle
In welcher Hülle soll das funktionieren? Nicht schlagen. Kein Fisch (hat $argvaber nicht $#argv- $argv[(count $argv)]wirkt bei Fisch).
Beni Cherniavsky-Paskin
1

Dies ist Teil meiner Kopierfunktion:

eval echo $(echo '$'"$#")

Gehen Sie folgendermaßen vor, um sie in Skripten zu verwenden:

a=$(eval echo $(echo '$'"$#"))

Erläuterung (am meisten verschachtelt zuerst):

  1. $(echo '$'"$#")Gibt zurück, $[nr]wo [nr]die Anzahl der Parameter ist. ZB die Saite $123(nicht erweitert).
  2. echo $123 Gibt bei Auswertung den Wert des 123. Parameters zurück.
  3. evalerweitert sich einfach $123auf den Wert des Parameters, z last_arg. Dies wird als Zeichenfolge interpretiert und zurückgegeben.

Arbeitet ab Mitte 2015 mit Bash.

Esavier
quelle
Der Eval-Ansatz wurde hier bereits viele Male vorgestellt, aber dieser hat eine Erklärung, wie er funktioniert. Könnte weiter verbessert werden, aber es lohnt sich immer noch, es zu behalten.
Palec
0
#! /bin/sh

next=$1
while [ -n "${next}" ] ; do
  last=$next
  shift
  next=$1
done

echo $last
Craig Trader
quelle
Dies schlägt fehl, wenn ein Argument die leere Zeichenfolge ist, funktioniert jedoch in 99,9% der Fälle.
Thomas
0

Versuchen Sie das folgende Skript, um das letzte Argument zu finden

 # cat arguments.sh
 #!/bin/bash
 if [ $# -eq 0 ]
 then
 echo "No Arguments supplied"
 else
 echo $* > .ags
 sed -e 's/ /\n/g' .ags | tac | head -n1 > .ga
 echo "Last Argument is: `cat .ga`"
 fi

Ausgabe:

 # ./arguments.sh
 No Arguments supplied

 # ./arguments.sh testing for the last argument value
 Last Argument is: value

Vielen Dank.

Ranjithkumar T.
quelle
Ich vermute, dass dies mit ./arguments.sh "letzter Wert"
Thomas
Vielen Dank, dass Sie Thomas überprüft haben. Ich habe versucht, das as-Skript so auszuführen, wie Sie es mit # ./arguments.sh "last value" angegeben haben. Das letzte Argument lautet: value funktioniert jetzt einwandfrei. # ./arguments.sh "letzte Wertprüfung mit double" Letztes Argument ist: double
Ranjithkumar T
Das Problem ist, dass das letzte Argument 'letzter Wert' und nicht Wert war. Der Fehler wird durch das Argument verursacht, das ein Leerzeichen enthält.
Thomas
0

Es gibt einen viel prägnanteren Weg, dies zu tun. Argumente für ein Bash-Skript können in ein Array eingefügt werden, was den Umgang mit den Elementen erheblich vereinfacht. Das folgende Skript gibt immer das letzte Argument aus, das an ein Skript übergeben wurde.

  argArray=( "$@" )                        # Add all script arguments to argArray
  arrayLength=${#argArray[@]}              # Get the length of the array
  lastArg=$((arrayLength - 1))             # Arrays are zero based, so last arg is -1
  echo ${argArray[$lastArg]}

Beispielausgabe

$ ./lastarg.sh 1 2 buckle my shoe
shoe
Tekbot
quelle
0

Parametererweiterung verwenden (übereinstimmenden Anfang löschen):

args="$@"
last=${args##* }

Es ist auch einfach, alles vorletzt zu bekommen:

prelast=${args% *}
Jurij
quelle
Mögliches (schlechtes) Duplikat von stackoverflow.com/a/37601842/1446229
Xerz
Dies funktioniert nur, wenn das letzte Argument kein Leerzeichen enthält.
Palec
Ihr Prelast schlägt fehl, wenn das letzte Argument ein Satz in doppelten Anführungszeichen ist, wobei dieser Satz ein Argument sein soll.
Stephane
0

Verwenden Sie den speziellen Parameter, um das letzte Argument des zuletzt verwendeten Befehls zurückzugeben:

$_

In diesem Fall funktioniert es, wenn es im Skript verwendet wird, bevor ein anderer Befehl aufgerufen wurde.

Thain
quelle
Dies ist nicht das, was die Frage stellt
retnikt
-1

Einfach benutzen !$.

$ mkdir folder
$ cd !$ # will run: cd folder
r1oga
quelle
Dies ist nicht das, was die Frage stellt
retnikt