Obtener el último argumento pasado a un script de shell

272

$1Es el primer argumento.
$@es todos ellos

¿Cómo puedo encontrar el último argumento pasado a un script de shell?

Thomas
fuente
Estaba usando bash, pero la solución más portátil mejor.
Thomas
10
use también puede usar $ {! #}
Prateek Joshi el
11
Por solo bash , la respuesta de Kevin Little propone lo simple ${!#}. Pruébalo usando bash -c 'echo ${!#}' arg1 arg2 arg3. Para bash , ksh y zsh , la respuesta de Dennis Williamson propone ${@: -1}. Además ${*: -1}también se puede utilizar. Pruébalo usando zsh -c 'echo ${*: -1}' arg1 arg2 arg3. Pero eso no funciona para dash , csh y tcsh .
olibre
2
${!#}, a diferencia ${@: -1}, también funciona con la expansión de parámetros. Puedes probarlo con bash -c 'echo ${!#%.*}' arg1.out arg2.out arg3.out.
Arch Stanton

Respuestas:

177

Esto es un poco hack:

for last; do true; done
echo $last

Este también es bastante portátil (nuevamente, debería funcionar con bash, ksh y sh) y no cambia los argumentos, lo que podría ser bueno.

Utiliza el hecho de que forimplícitamente recorre los argumentos si no le dice qué hacer, y el hecho de que las variables de bucle no tienen un alcance: mantienen el último valor en el que se establecieron.

Laurence Gonsalves
fuente
10
@ MichałŠrajer, creo que te refieres a dos puntos y no a una coma;)
Paweł Nadolski
2
@ MichałŠrajer truees parte de POSIX .
Rufflewind
44
Con un viejo Solaris, con el viejo bourne shell (no POSIX), tengo que escribir "por último en" $ @ "; hacer verdad; hecho"
mcoolive
8
@mcoolive @LaurenceGolsalves además de ser más portátil, for last in "$@"; do :; donetambién hace que la intención sea mucho más clara.
Adrian Günter
1
@mcoolive: incluso funciona en el shell bourne de Unix v7 desde 1979. No puede ser más portátil si se ejecuta en v7 sh y POSIX sh :)
Dominik R
291

Esto es solo Bash:

echo "${@: -1}"
Pausado hasta nuevo aviso.
fuente
43
Para aquellos (como yo) que se preguntan por qué se necesita el espacio, man bash tiene esto que decir al respecto:> Tenga en cuenta que un desplazamiento negativo debe estar separado del colon por al menos un espacio para evitar confundirse con: expansión.
foo
1
He estado usando esto y se rompe en MSYS2 bash solo en Windows. Extraño.
Steven Lu
2
Nota: Esta respuesta funciona para todos los arreglos Bash, a diferencia de lo ${@:$#}que solo funciona $@. Si tuviera que copiar $@a una nueva matriz con arr=("$@"), ${arr[@]:$#}sería indefinido. Esto se debe a que $@tiene un elemento 0 que no está incluido en las "$@"expansiones.
Sr. Llama
@ Mr.Llama: Otro lugar que debe evitarse $#es al iterar sobre matrices, ya que, en Bash, las matrices son escasas y, si bien $#muestran el número de elementos en la matriz, no necesariamente apunta al último elemento (o elemento + 1). En otras palabras, uno no debe hacer for ((i = 0; i++; i < $#)); do something "${array[$i]}"; doney en su lugar hacer for element in "${array[@]}"; do something "$element"; doneo iterar sobre los índices: for index in "${!array[@]}"; do something "$index" "${array[$index]}"; donesi necesita hacer algo con los valores de los índices.
Pausado hasta nuevo aviso.
80
$ set quick brown fox jumps

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

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

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

El espacio es necesario para que no se interprete como un valor predeterminado .

Tenga en cuenta que esto es solo bash.

Steven Penny
fuente
9
La mejor respuesta, ya que también incluye el penúltimo argumento. ¡Gracias!
e40
1
Steven, no sé qué hiciste para aterrizar en el Penalty Box, pero me encanta tu trabajo aquí.
Bruno Bronosky
44
si. simplemente lo mejor. todo menos comando y último argumento ${@: 1:$#-1}
Dyno Fu
1
@DynoFu gracias por eso, respondiste mi siguiente pregunta. Entonces, un cambio podría verse así: echo ${@: -1} ${@: 1:$#-1}donde el último se convierte en el primero y el resto se desliza hacia abajo
Mike
73

La respuesta más simple para bash 3.0 o superior es

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

Eso es.

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

Salida es:

$ lastarg 1 2 3 4 "5 6 7"
5 6 7
5 6 7
1
2
3
4
5 6 7
Kevin Little
fuente
1
$BASH_ARGVno funciona dentro de una función bash (a menos que esté haciendo algo mal).
Big McLargeHuge
1
BASH_ARGV tiene los argumentos cuando se llamó a bash (o para una función), no la lista actual de argumentos posicionales.
Isaac
Tenga en cuenta también que lo que BASH_ARGVle dará es el valor que fue el último argumento que se le dio, en lugar de simplemente "el último valor". Por ejemplo !: si proporciona un solo argumento, entonces llama a shift, ${@:$#}no producirá nada (¡porque cambió el único argumento!), Sin embargo, BASH_ARGVtodavía le dará ese (anteriormente) último argumento.
Steven Lu
30

Utilice la indexación combinada con la longitud de:

echo ${@:${#@}} 

Tenga en cuenta que esto es solo bash.

Mark Byers
fuente
24
Más corto:echo ${@:$#}
pausa hasta nuevo aviso.
1
echo $ (@ & $; & ^ $ &% @ ^! @% ^ # ** # & @ * # @ * # @ (& * # _ ** ^ @ ^ & (^ @ & * & @)
Atul
29

Lo siguiente funcionará para usted. La @ es para una variedad de argumentos. : significa en. $ # es la longitud de la matriz de argumentos. Entonces el resultado es el último elemento:

${@:$#} 

Ejemplo:

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

Tenga en cuenta que esto es solo bash.

Tahsin Turkoz
fuente
Si bien el ejemplo es para una función, los scripts también funcionan de la misma manera. Me gusta esta respuesta porque es clara y concisa.
Jonah Braun
1
Y no es hacky. Utiliza características explícitas del lenguaje, no efectos secundarios o qwerks especiales. Esta debería ser la respuesta aceptada.
musicin3d
25

Encontré esto cuando buscaba separar el último argumento de todos los anteriores. Si bien algunas de las respuestas obtienen el último argumento, no son de mucha ayuda si necesita todos los otros argumentos también. Esto funciona mucho mejor:

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

Tenga en cuenta que esto es solo bash.

AgileZebra
fuente
3
La respuesta de Steven Penny es un poco más agradable: usar ${@: -1}para el último y ${@: -2:1}para el segundo último (y así sucesivamente ...). Ejemplo: bash -c 'echo ${@: -1}' prog 0 1 2 3 4 5 6impresiones 6. Para seguir con este enfoque actual de AgileZebra, use ${@:$#-1:1}para obtener el segundo último . Ejemplo: bash -c 'echo ${@:$#-1:1}' prog 0 1 2 3 4 5 6impresiones 5. (y ${@:$#-2:1}para obtener el último último y así sucesivamente ...)
olibre
44
La respuesta de AgileZebra proporciona una forma de obtener todos menos los últimos argumentos, por lo que no diría que la respuesta de Steven la reemplaza. Sin embargo, parece que no hay razón para usar $((...))para restar el 1, simplemente puede usar ${@:1:$# - 1}.
dkasak
Gracias dkasak Actualizado para reflejar su simplificación.
AgileZebra
22

Esto funciona en todos los shells compatibles con POSIX:

eval last=\${$#}

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

poiuz
fuente
2
Ver este comentario adjunto a una respuesta idéntica anterior.
Pausado hasta nuevo aviso.
1
La solución portátil más simple que veo aquí. Este no tiene ningún problema de seguridad, @DennisWilliamson, la cita empírica parece ser correcta, a menos que haya una manera de establecer $#una cadena arbitraria (no lo creo). eval last=\"\${$#}\"También funciona y es obviamente correcto. No veo por qué las citas no son necesarias.
Palec
1
Para bash , zsh , dash y ksh eval last=\"\${$#}\" está bien. Pero para csh y tcsh uso eval set last=\"\${$#}\". Vea este ejemplo: tcsh -c 'eval set last=\"\${$#}\"; echo "$last"' arg1 arg2 arg3.
olibre
12

Aquí está la solución mía:

  • bastante portátil (todos los POSIX sh, bash, ksh, zsh) deberían funcionar
  • no cambia los argumentos originales (cambia una copia).
  • no usa el mal eval
  • no recorre toda la lista
  • no usa herramientas externas

Código:

ntharg() {
    shift $1
    printf '%s\n' "$1"
}
LAST_ARG=`ntharg $# "$@"`
Michał Šrajer
fuente
1
Gran respuesta: corta, portátil, segura. ¡Gracias!
Ján Lalinský
2
Esta es una gran idea, pero tengo un par de sugerencias: en primer lugar, las citas deben agregarse tanto alrededor "$1"como "$#"(vea esta gran respuesta unix.stackexchange.com/a/171347 ). En segundo lugar, echolamentablemente no es portátil (especialmente para -n), por lo que printf '%s\n' "$1"debe usarse en su lugar.
joki
gracias @joki Trabajé con muchos sistemas unix diferentes y tampoco confiaría en echo -nellos, sin embargo, no estoy al tanto de ningún sistema posix donde echo "$1"fallara. De todos modos, printfes más predecible, actualizado.
Michał Šrajer
@ MichałŠrajer considere el caso donde "$ 1" es "-n" o "-", por ejemplo, ntharg 1 -no ntharg 1 --puede producir resultados diferentes en varios sistemas. ¡El código que tienes ahora es seguro!
joki
10

De las soluciones más antiguas a las más nuevas:

La solución más portátil, incluso más antigua sh(funciona con espacios y caracteres globales) (sin bucle, más rápido):

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

Desde la versión 2.01 de bash

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

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

Para ksh, zsh y bash:

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

Y para "penúltimo":

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

Usar printf para solucionar cualquier problema con argumentos que comienzan con un guión (como -n ).

Para todos los shells y para mayores sh(funciona con espacios y caracteres glob) es:

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

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

O, si desea establecer una lastvar:

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

Y para "penúltimo":

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

Si está utilizando Bash> = 3.0

echo ${BASH_ARGV[0]}
dusan
fuente
También para el penúltimo argumento, hazloecho ${BASH_ARGV[1]}
Steven Penny
1
ARGVsignifica "vector de argumento".
Serge Stroobandt
5

Para bash, este comentario sugirió lo muy elegante:

echo "${@:$#}"

Como beneficio adicional, esto también funciona en zsh.

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

Esto cambia los argumentos por el número de argumentos menos 1, y devuelve el primer (y único) argumento restante, que será el último.

Solo probé en bash, pero también debería funcionar en sh y ksh.

Laurence Gonsalves
fuente
11
shift $(($# - 1))- No necesita una utilidad externa. Funciona en Bash, ksh, zsh y dash.
Pausado hasta nuevo aviso.
@ Dennis: ¡Qué bien! No sabía sobre la $((...))sintaxis.
Laurence Gonsalves
Desea utilizar printf '%s\n' "$1"para evitar un comportamiento inesperado de echo(por ejemplo, para -n).
joki
2

Si desea hacerlo de una manera no destructiva, una forma es pasar todos los argumentos a una función y devolver el último:

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

Si realmente no te importa mantener los otros argumentos, no lo necesitas en una función, pero me cuesta pensar en una situación en la que nunca quieras conservar los otros argumentos a menos que ya hayan sido procesados, en cuyo caso usaría el método process / shift / process / shift / ... para procesarlos secuencialmente.

Asumo aquí que desea conservarlos porque no ha seguido el método secuencial. Este método también maneja el caso donde no hay argumentos, devolviendo "". Puede ajustar fácilmente ese comportamiento insertando la elsecláusula comentada .

paxdiablo
fuente
2

Para tcsh:

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

Estoy bastante seguro de que esta sería una solución portátil, a excepción de la asignación.

Perfecto64
fuente
Sé que publicaste esto hace mucho tiempo, pero esta solución es genial, ¡me alegra que alguien haya publicado una tcsh!
usuario3295674
2

Encontré la respuesta de @ AgileZebra (más el comentario de @ starfry) la más útil, pero se establece headsen un escalar. Una matriz es probablemente más útil:

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

Tenga en cuenta que esto es solo bash.

EndlosSchleife
fuente
¿Cómo convertirías las cabezas en una matriz?
Stephane
Ya lo hice agregando los paréntesis, por ejemplo, echo "${heads[-1]}"imprime el último elemento en heads. ¿O me estoy perdiendo algo?
EndlosSchleife
1

Una solución usando eval:

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

echo $last
Mikael S
fuente
77
evaluar para referencia indirecta es exagerado, sin mencionar las malas prácticas, y una gran preocupación de seguridad (el valor no está entre comillas ni fuera de $ (). Bash tiene una sintaxis incorporada para referencias indirectas, para cualquier var: !entonces last="${!#}"usaría el mismo . enfoque (referencia indirecta en $ #) en un lugar más seguro, compacto, incorporado, de manera mucho sano y adecuadamente citado.
MestreLion
Vea una forma más limpia de realizar lo mismo en otra respuesta .
Palec
Las cotizaciones de @MestreLion no son necesarias en el RHS de =.
Tom Hale
1
@TomHale: verdadero para el caso particular de ${!#}, pero no en general: todavía se necesitan comillas si el contenido contiene espacios en blanco literales, como last='two words'. Solo $()es seguro para espacios en blanco independientemente del contenido.
MestreLion
1

Después de leer las respuestas anteriores, escribí un script de shell Q&D (debería funcionar en sh y bash) para ejecutar g ++ en PGM.cpp para producir una imagen ejecutable PGM. Se supone que el último argumento en la línea de comando es el nombre del archivo (.cpp es opcional) y todos los demás argumentos son opciones.

#!/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.
fuente
1

Lo siguiente establecerá el LASTúltimo argumento sin cambiar el entorno actual:

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

Si ya no se necesitan otros argumentos y se pueden cambiar, se puede simplificar a:

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

Por razones de portabilidad siguientes:

shift $(($#-1));

se puede reemplazar con:

shift `expr $# - 1`

Reemplazando también $()con backquotes obtenemos:

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

Ahora solo necesito agregar algo de texto porque mi respuesta fue demasiado corta para publicar. Necesito agregar más texto para editar.

frank1rizzo
fuente
¿En qué caparazón se supone que funciona esto? No bash. No pescado (tiene $argvpero no $#argv- $argv[(count $argv)]funciona en pescado).
Beni Cherniavsky-Paskin
1

Esto es parte de mi función de copia:

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

Para usar en scripts, haga esto:

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

Explicación (la mayoría anidada primero):

  1. $(echo '$'"$#")vuelve $[nr]donde[nr] está el número de parámetros. Por ejemplo, la cadena $123(sin expandir).
  2. echo $123 devuelve el valor del parámetro 123, cuando se evalúa.
  3. evalsimplemente se expande $123al valor del parámetro, p. ej.last_arg . Esto se interpreta como una cadena y se devuelve.

Trabaja con Bash a partir de mediados de 2015.

Esavier
fuente
El enfoque eval ya se ha presentado aquí muchas veces, pero este tiene una explicación de cómo funciona. Podría mejorarse aún más, pero aún vale la pena conservarlo.
Palec
0
#! /bin/sh

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

echo $last
Craig Trader
fuente
Esto fallará si un argumento es la cadena vacía, pero funcionará en el 99.9% de los casos.
Thomas
0

Pruebe el siguiente script para encontrar el último argumento

 # 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

Salida:

 # ./arguments.sh
 No Arguments supplied

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

Gracias.

Ranjithkumar T
fuente
Sospecho que esto podría fallar con ./arguments.sh "último valor"
Thomas
Gracias por revisar a Thomas, he intentado ejecutar el script como me mencionó # ./arguments.sh "last value" Last Argument is: value está funcionando bien ahora. # ./arguments.sh "verificación del último valor con doble" El último argumento es: doble
Ranjithkumar T
El problema es que el último argumento fue 'último valor' y no valor. El error es causado por el argumento que contiene un espacio.
Thomas
0

Hay una manera mucho más concisa de hacer esto. Los argumentos de una secuencia de comandos bash se pueden incorporar a una matriz, lo que simplifica mucho el manejo de los elementos. El siguiente script siempre imprimirá el último argumento pasado a un 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]}

Salida de muestra

$ ./lastarg.sh 1 2 buckle my shoe
shoe
tekbot
fuente
0

Usando la expansión de parámetros (eliminar el comienzo coincidente):

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

También es fácil obtener todo antes del último:

prelast=${args% *}
Jurij
fuente
Posible duplicado (pobre) de stackoverflow.com/a/37601842/1446229
Xerz
Esto solo funciona si el último argumento no contiene un espacio.
Palec
Su prelast falla si el último argumento es una oración encerrada entre comillas dobles, y dicha oración se supone que es un argumento.
Stephane
0

Para devolver el último argumento del comando utilizado más recientemente, use el parámetro especial:

$_

En este caso, funcionará si se usa dentro del script antes de que se haya invocado otro comando.

Thain
fuente
Esta no es la pregunta
retnikt
-1

Solo úsalo !$.

$ mkdir folder
$ cd !$ # will run: cd folder
r1oga
fuente
Esta no es la pregunta
retnikt