Ottenere l'ultimo argomento passato a uno script di shell


272

$1è il primo argomento.
$@sono tutti loro.

Come posso trovare l'ultimo argomento passato a uno script di shell?


Stavo usando bash, ma più soluzione portatile è, meglio è.
Thomas,


10
use può anche usare $ {! #}
Prateek Joshi il

11
Per solo bash , la risposta di Kevin Little propone il semplice ${!#}. Provalo usando bash -c 'echo ${!#}' arg1 arg2 arg3. Per bash , ksh e zsh , la risposta di Dennis Williamson propone ${@: -1}. Inoltre ${*: -1}può anche essere usato. Provalo usando zsh -c 'echo ${*: -1}' arg1 arg2 arg3. Ma questo non funziona per dash , csh e tcsh .
olibre,

2
${!#}, a differenza ${@: -1}, funziona anche con l'espansione dei parametri. Puoi provarlo con bash -c 'echo ${!#%.*}' arg1.out arg2.out arg3.out.
Arch Stanton,

Risposte:


177

Questo è un po 'un trucco:

for last; do true; done
echo $last

Anche questo è abbastanza portatile (di nuovo, dovrebbe funzionare con bash, ksh e sh) e non sposta gli argomenti, il che potrebbe essere carino.

Usa il fatto che forricopre implicitamente gli argomenti se non gli dici cosa ricorrere al loop e il fatto che per le variabili del loop non sono comprese: mantengono l'ultimo valore su cui sono state impostate.


10
@ MichałŠrajer, penso che volevi dire colon e non virgola;)
Paweł Nadolski,

2
@ MichałŠrajer truefa parte di POSIX .
Rufflewind,

4
Con un vecchio Solaris, con la vecchia shell bourne (non POSIX), devo scrivere "per ultimo in" $ @ "; do true; done"
mcoolive

8
@mcoolive @LaurenceGolsalves oltre ad essere più portatile, for last in "$@"; do :; donerende l'intento molto più chiaro.
Adrian Günter,

1
@mcoolive: funziona anche nella shell bourne unix v7 del 1979. non puoi essere più portatile se funziona sia su v7 sh che su POSIX sh :)
Dominik R

291

Questo è solo Bash:

echo "${@: -1}"

43
Per quelli (come me) che si chiedono perché sia ​​necessario lo spazio, man bash ha questo da dire al riguardo:> Nota che un offset negativo deve essere separato dai due punti da almeno uno spazio per evitare di essere confuso con l'espansione: -.
foo,

1
Sto usando questo e si rompe in bash MSYS2 solo in Windows. Bizzarro.
Steven Lu

2
Nota: questa risposta funziona per tutti gli array Bash, a differenza del ${@:$#}quale funziona solo $@. Se si dovesse copiare $@su un nuovo array con arr=("$@"), ${arr[@]:$#}sarebbe indefinito. Questo perché $@ha un 0 ° elemento che non è incluso nelle "$@"espansioni.
Mr. Llama,

@ Mr.Llama: Un altro posto da evitare $#è quando si ripetono le matrici poiché, in Bash, le matrici sono scarse e mentre $#mostreranno il numero di elementi nella matrice non punta necessariamente all'ultimo elemento (o elemento + 1). In altre parole, non si dovrebbe fare for ((i = 0; i++; i < $#)); do something "${array[$i]}"; donee invece fare for element in "${array[@]}"; do something "$element"; doneo iterare sugli indici: for index in "${!array[@]}"; do something "$index" "${array[$index]}"; donese è necessario fare qualcosa con i valori degli indici.
In pausa fino a nuovo avviso.

80
$ set quick brown fox jumps

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

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

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

Lo spazio è necessario in modo che non venga interpretato come valore predefinito .

Nota che questo è solo bash.


9
La migliore risposta, dal momento che include anche il penultimo argomento. Grazie!
e40,

1
Steven, non so cosa hai fatto per atterrare nella Cassa di rigore, ma adoro il tuo lavoro qui.
Bruno Bronosky,

4
sì. semplicemente il migliore. tutti tranne il comando e l'ultimo argomento ${@: 1:$#-1}
Dyno Fu

1
@DynoFu grazie per quello, hai risposto alla mia prossima domanda. Quindi un turno potrebbe apparire come:, echo ${@: -1} ${@: 1:$#-1}dove l'ultimo diventa per primo e il resto scivola giù
Mike

73

La risposta più semplice per bash 3.0 o successive è

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

Questo è tutto.

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

L'output è:

$ lastarg 1 2 3 4 "5 6 7"
5 6 7
5 6 7
1
2
3
4
5 6 7

1
$BASH_ARGVnon funziona all'interno di una funzione bash (a meno che non stia facendo qualcosa di sbagliato).
Big McLarge, enorme,

1
BASH_ARGV ha gli argomenti quando è stato chiamato bash (o ad una funzione) non il presente elenco di argomenti posizionali.
Isaac,

Nota anche che cosa BASH_ARGVti produrrà è il valore che è stato dato l'ultimo argomento, anziché semplicemente "l'ultimo valore". Ad esempio !: se fornisci un singolo argomento, poi chiami shift, ${@:$#}non produrrà nulla (perché hai spostato l'unico e unico argomento!), Tuttavia, BASH_ARGVti darà comunque quell'ultimo (precedentemente) argomento.
Steven Lu,

30

Usa l'indicizzazione combinata con la lunghezza di:

echo ${@:${#@}} 

Nota che questo è solo bash.


24
Più breve:echo ${@:$#}
In pausa fino a nuovo avviso.

1
echo $ (@ & $; & ^ $ &% @ ^! @% ^ # ** # & @ * # @ * # @ (& * # _ ** ^ @ ^ & (^ @ & * & @)
Atul,

29

Quanto segue funzionerà per te. @ È per una serie di argomenti. : significa a. $ # è la lunghezza dell'array di argomenti. Quindi il risultato è l'ultimo elemento:

${@:$#} 

Esempio:

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

Nota che questo è solo bash.


Mentre l'esempio è per una funzione, anche gli script funzionano allo stesso modo. Mi piace questa risposta perché è chiara e concisa.
Jonah Braun,

1
E non è confuso. Utilizza funzionalità esplicite del linguaggio, non effetti collaterali o qwerk speciali. Questa dovrebbe essere la risposta accettata.
musicin3d

25

Trovato questo quando si cerca di separare l'ultimo argomento da tutti i precedenti. Mentre alcune delle risposte ottengono l'ultimo argomento, non sono di grande aiuto se hai bisogno anche di tutti gli altri argomenti. Funziona molto meglio:

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

Nota che questo è solo bash.


3
La risposta di Steven Penny è un po 'più carina: usa ${@: -1}per l' ultimo e ${@: -2:1}per il secondo ultimo (e così via ...). Esempio: bash -c 'echo ${@: -1}' prog 0 1 2 3 4 5 6stampe 6. Per rimanere con l'attuale approccio di AgileZebra, usa ${@:$#-1:1}per ottenere il secondo ultimo . Esempio: bash -c 'echo ${@:$#-1:1}' prog 0 1 2 3 4 5 6stampe 5. (e ${@:$#-2:1}per ottenere il terzo ultimo e così via ...)
olibre il

4
La risposta di AgileZebra fornisce un modo per ottenere tutti tranne gli ultimi argomenti, quindi non direi che la risposta di Steven la sostituisce. Tuttavia, non sembra esserci alcun motivo $((...))per sottrarre 1, che puoi semplicemente usare ${@:1:$# - 1}.
dkasak,

Grazie dkasak. Aggiornato per riflettere la tua semplificazione.
AgileZebra,

22

Funziona con tutte le shell compatibili con POSIX:

eval last=\${$#}

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


2
Vedi questo commento allegato a una risposta identica precedente.
In pausa fino a nuovo avviso.

1
La soluzione portatile più semplice che vedo qui. Questo non ha alcun problema di sicurezza, @DennisWilliamson, la citazione empiricamente sembra essere fatta bene, a meno che non ci sia un modo per impostare $#una stringa arbitraria (non credo). eval last=\"\${$#}\"funziona anche ed è ovviamente corretto. Non vedo perché le virgolette non sono necessarie.
Palec,

1
Per bash , zsh , dash e ksh eval last=\"\${$#}\" va bene. Ma per csh e tcsh uso eval set last=\"\${$#}\". Vedere questo esempio: tcsh -c 'eval set last=\"\${$#}\"; echo "$last"' arg1 arg2 arg3.
olibre,

12

Ecco la mia soluzione:

  • abbastanza portatile (tutti i POSIX sh, bash, ksh, zsh) dovrebbero funzionare
  • non sposta gli argomenti originali (sposta una copia).
  • non usa il male eval
  • non scorre l'intera lista
  • non utilizza strumenti esterni

Codice:

ntharg() {
    shift $1
    printf '%s\n' "$1"
}
LAST_ARG=`ntharg $# "$@"`

1
Ottima risposta: breve, portatile, sicura. Grazie!
Ján Lalinský,

2
Questa è una grande idea, ma ho un paio di suggerimenti: in primo luogo la citazione dovrebbe essere aggiunta sia in giro "$1"che "$#"(vedi questa grande risposta unix.stackexchange.com/a/171347 ). In secondo luogo, echoè purtroppo non portatile (in particolare per -n), quindi printf '%s\n' "$1"dovrebbe essere utilizzato invece.
joki,

grazie @joki Ho lavorato con molti sistemi unix diversi e non mi fiderei neanche di me echo -n, tuttavia non sono a conoscenza di alcun sistema posix in cui echo "$1"fallirebbe. Ad ogni modo, printfè davvero più prevedibile - aggiornato.
Michał Šrajer,

@ MichałŠrajer considera il caso in cui "$ 1" è "-n" o "-", quindi ad esempio ntharg 1 -no ntharg 1 --può dare risultati diversi su vari sistemi. Il codice che hai ora è sicuro!
joki,

10

Dalle soluzioni più vecchie a quelle più recenti:

La soluzione più portatile, anche più vecchia sh(funziona con spazi e caratteri glob) (no loop, più veloce):

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

Dalla versione 2.01 di bash

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

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

Per ksh, zsh e bash:

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

E per "prossimo all'ultimo":

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

Uso di printf per risolvere eventuali problemi con argomenti che iniziano con un trattino (come -n).

Per tutte le shell e per le versioni precedenti sh(funziona con spazi e caratteri glob) è:

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

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

Oppure, se si desidera impostare un lastvar:

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

E per "prossimo all'ultimo":

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



3
shift `expr $# - 1`
echo "$1"

Ciò sposta gli argomenti in base al numero di argomenti meno 1 e restituisce il primo (e unico) argomento rimanente, che sarà l'ultimo.

Ho provato solo in bash, ma dovrebbe funzionare anche in sh e ksh.


11
shift $(($# - 1))- nessuna necessità di un'utilità esterna. Funziona in Bash, ksh, zsh e dash.
In pausa fino a nuovo avviso.

@Dennis: bello! Non sapevo della $((...))sintassi.
Laurence Gonsalves,

Si desidera utilizzare printf '%s\n' "$1"per evitare comportamenti imprevisti da echo(ad esempio per -n).
joki

2

Se vuoi farlo in modo non distruttivo, un modo è passare tutti gli argomenti a una funzione e restituire l'ultimo:

#!/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

Se in realtà non ti interessa mantenere gli altri argomenti, non ne hai bisogno in una funzione, ma faccio fatica a pensare a una situazione in cui non vorrai mai mantenere gli altri argomenti a meno che non siano già stati elaborati, nel qual caso utilizzerei il metodo process / shift / process / shift / ... per elaborarli in sequenza.

Presumo che tu voglia mantenerli perché non hai seguito il metodo sequenziale. Questo metodo gestisce anche il caso in cui non ci siano argomenti, restituendo "". È possibile regolare facilmente tale comportamento inserendo la elseclausola commentata .


2

Per tcsh:

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

Sono abbastanza sicuro che questa sarebbe una soluzione portatile, ad eccezione del compito.


So che l'hai pubblicato per sempre, ma questa soluzione è fantastica - felice che qualcuno ne abbia pubblicato uno!
user3295674,

2

Ho trovato la risposta di @ AgileZebra (più il commento di @ starfry) la più utile, ma è impostata headssu uno scalare. Un array è probabilmente più utile:

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

Nota che questo è solo bash.


Come trasformereste le teste in un array?
Stephane

L'ho già fatto aggiungendo le parentesi, ad esempio echo "${heads[-1]}"stampa l'ultimo elemento in heads. Oppure mi sfugge qualcosa?
EndlosSchleife,

1

Una soluzione che utilizza eval:

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

echo $last

7
valutazione per riferimento indiretto è eccessiva, per non parlare delle cattive pratiche, e di una grande preoccupazione per la sicurezza (il valore non è quotato in eco o al di fuori di $ (). Bash ha una sintassi integrata per riferimenti indiretti, per qualsiasi var: !Quindi last="${!#}"userei lo stesso approccio (riferimento indiretto su $ #) in un modo molto più sicuro, compatto, integrato, sano e correttamente citato
MestreLion

Vedi un modo più pulito di eseguire lo stesso in un'altra risposta .
Palec,

Le citazioni di @MestreLion non sono necessarie sull'RHS di =.
Tom Hale,

1
@TomHale: vero per il caso particolare di ${!#}, ma non in generale: le citazioni sono ancora necessarie se il contenuto contiene spazi bianchi letterali, come last='two words'. Solo gli $()spazi bianchi sono sicuri indipendentemente dal contenuto.
MestreLion,

1

Dopo aver letto le risposte sopra ho scritto uno script di shell Q&D (dovrebbe funzionare su sh e bash) per eseguire g ++ su PGM.cpp per produrre un'immagine eseguibile PGM. Presuppone che l'ultimo argomento sulla riga di comando sia il nome del file (.cpp è facoltativo) e tutti gli altri argomenti sono opzioni.

#!/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

1

Quanto segue imposterà LASTl'ultimo argomento senza modificare l'ambiente corrente:

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

Se altri argomenti non sono più necessari e possono essere spostati, è possibile semplificarli per:

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

Per motivi di portabilità seguenti:

shift $(($#-1));

può essere sostituito con:

shift `expr $# - 1`

Sostituendo anche $()con i backquotes otteniamo:

LAST=`{
   shift \`expr $# - 1\`
   echo $1
}`
echo $LAST

1
echo $argv[$#argv]

Ora ho solo bisogno di aggiungere del testo perché la mia risposta era troppo breve per pubblicare. Devo aggiungere più testo per modificarlo.


In quale shell dovrebbe funzionare? Non bash. Non pesce (ha $argvma non $#argv- $argv[(count $argv)]funziona nei pesci).
Beni Cherniavsky-Paskin,

1

Questo fa parte della mia funzione di copia:

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

Per utilizzare negli script, procedere come segue:

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

Spiegazione (prima la maggior parte nidificata):

  1. $(echo '$'"$#")ritorna $[nr]dove [nr]è il numero di parametri. Ad esempio la stringa $123(non espansa).
  2. echo $123 restituisce il valore del 123 ° parametro, quando valutato.
  3. evalsi espande semplicemente $123al valore del parametro, ad es last_arg. Questo viene interpretato come una stringa e restituito.

Funziona con Bash dalla metà del 2015.


L'approccio eval è già stato presentato qui molte volte, ma questo ha una spiegazione di come funziona. Potrebbe essere ulteriormente migliorato, ma vale comunque la pena mantenerlo.
Palec,

0
#! /bin/sh

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

echo $last

Ciò fallirà se un argomento è la stringa vuota, ma funzionerà nel 99,9% dei casi.
Thomas,

0

Prova lo script seguente per trovare l'ultimo argomento

 # 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

Produzione:

 # ./arguments.sh
 No Arguments supplied

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

Grazie.


Sospetto che questo fallirebbe con ./arguments.sh "ultimo valore"
Thomas,

Grazie per aver controllato Thomas, ho provato a eseguire lo script come nel tuo mentinoed # ./arguments.sh "ultimo valore" L'ultimo argomento è: il valore ora funziona bene. # ./arguments.sh "controllo dell'ultimo valore con doppio" L'ultimo argomento è: doppio
Ranjithkumar T

Il problema è che l'ultimo argomento era "ultimo valore" e non valore. L'errore è causato dall'argomento contenente uno spazio.
Thomas,

0

C'è un modo molto più conciso per farlo. Gli argomenti di uno script bash possono essere portati in un array, il che rende molto più semplice gestire gli elementi. Lo script seguente stamperà sempre l'ultimo argomento passato a uno script.

  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]}

Uscita campione

$ ./lastarg.sh 1 2 buckle my shoe
shoe

0

Utilizzando l'espansione dei parametri (elimina l'inizio corrispondente):

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

È anche facile ottenere tutto prima dell'ultimo:

prelast=${args% *}

Possible (scarso) duplicati di stackoverflow.com/a/37601842/1446229
Xerz

Funziona solo se l'ultimo argomento non contiene uno spazio.
Palec,

Il tuo prelast fallisce se l'ultimo argomento è una frase racchiusa tra virgolette, con la frase che si suppone sia un argomento.
Stephane

0

Per restituire l'ultimo argomento del comando utilizzato più di recente, utilizzare il parametro speciale:

$_

In questo caso funzionerà se viene utilizzato all'interno dello script prima che sia stato invocato un altro comando.


Non è questa la domanda
retnikt

-1

Basta usare !$.

$ mkdir folder
$ cd !$ # will run: cd folder

Non è questa la domanda
retnikt
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.