Obtendo o Último Argumento Passado para um Shell Script


272

$1é o primeiro argumento.
$@são todos eles.

Como posso encontrar o último argumento passado para um script de shell?


Eu estava usando o bash, mas quanto mais solução portátil melhor.
Thomas


10
uso também pode usar $ {#!}
Prateek Joshi

11
Para apenas festança , a resposta de Kevin Little propõe o simples ${!#}. Teste usando bash -c 'echo ${!#}' arg1 arg2 arg3. Para bash , ksh e zsh , a resposta de Dennis Williamson propõe ${@: -1}. Além disso, ${*: -1}também pode ser usado. Teste usando zsh -c 'echo ${*: -1}' arg1 arg2 arg3. Mas isso não funciona para o dash , csh e tcsh .
Olibre

2
${!#}, ao contrário ${@: -1}, também funciona com a expansão de parâmetros. Você pode testá-lo com bash -c 'echo ${!#%.*}' arg1.out arg2.out arg3.out.
Arch Stanton

Respostas:


177

Isso é meio que um hack:

for last; do true; done
echo $last

Este também é bastante portátil (novamente, deve funcionar com bash, ksh e sh) e não muda os argumentos, o que poderia ser bom.

Ele usa o fato de forimplicitamente repetir os argumentos se você não informar o que fazer loop e o fato de que as variáveis ​​de loop não têm escopo definido: elas mantêm o último valor para o qual foram definidas.


10
@ MichałŠrajer, eu acho que você quis cólon e não vírgula;)
Paweł Nadolski

2
@ MichałŠrajer truefaz parte do POSIX .
Rufflewind

4
Com um velho Solaris, com o shell Bourne antigo (não POSIX), eu tenho que escrever "para o último em '$ @'; fazer verdadeira; feito"
mcoolive

8
@mcoolive @LaurenceGolsalves, além de ser mais portátil, for last in "$@"; do :; donetambém torna a intenção muito mais clara.
Adrian Günter

1
@mcoolive: ele ainda funciona no unix v7 Bourne shell de 1979. você não pode obter mais portáteis se ele é executado em ambos sh v7 e POSIX sh :)
Dominik R

291

Isso é apenas Bash:

echo "${@: -1}"

43
Para aqueles (como eu) se perguntando por que o espaço é necessário, o man bash tem o que dizer sobre isso:> Observe que um deslocamento negativo deve ser separado do cólon por pelo menos um espaço para evitar ser confundido com a expansão: -.
foo

1
Eu tenho usado isso e ele quebra no MSYS2 bash apenas no Windows. Bizarro.
Steven Lu

2
Nota: Esta resposta funciona para todas as matrizes Bash, diferente da ${@:$#}que funciona apenas $@. Se você fosse copiar $@para uma nova matriz com arr=("$@"), ${arr[@]:$#}seria indefinido. Isso ocorre porque $@possui um 0º elemento que não está incluído nas "$@"expansões.
Mr. Llama

@ Mr.Llama: Outro lugar a evitar $#é ao iterar sobre matrizes, pois, no Bash, as matrizes são escassas e, embora $#mostrem o número de elementos na matriz, não estão necessariamente apontando para o último elemento (ou elemento + 1). Em outras palavras, não se deve fazer for ((i = 0; i++; i < $#)); do something "${array[$i]}"; donee, em vez disso, fazer for element in "${array[@]}"; do something "$element"; doneou iterar sobre os índices: for index in "${!array[@]}"; do something "$index" "${array[$index]}"; donese você precisar fazer algo com os valores dos índices.
Pausado até novo 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

O espaço é necessário para que ele não seja interpretado como um valor padrão .

Observe que isso é apenas para o bash.


9
Melhor resposta, pois também inclui o penúltimo argumento. Obrigado!
e40

1
Steven, não sei o que você fez para desembarcar na Caixa de Penalidades, mas estou amando seu trabalho aqui.
Bruno Bronosky

4
sim. simplesmente o melhor. tudo menos comando e último argumento ${@: 1:$#-1}
Dyno Fu

1
@DynoFu obrigado por isso, você respondeu minha próxima pergunta. Portanto, uma mudança pode parecer:, echo ${@: -1} ${@: 1:$#-1}onde a última se torna a primeira e o resto desliza para baixo #
Mike

73

A resposta mais simples para o bash 3.0 ou superior é

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

É isso aí.

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

A saída é:

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

1
$BASH_ARGVnão funciona dentro de uma função bash (a menos que eu esteja fazendo algo errado).
Big McLargeHuge

1
O BASH_ARGV possui os argumentos quando o bash foi chamado (ou para uma função) e não a lista atual de argumentos posicionais.
Isaac

Observe também o BASH_ARGVque lhe renderá é o valor que foi o último argumento que foi dado, em vez de simplesmente "o último valor". Por exemplo !: se você fornecer um único argumento, então chamar shift, ${@:$#}não produzirá nada (porque você alterou o primeiro e único argumento!), No entanto, BASH_ARGVainda fornecerá esse último argumento (anteriormente).
Steven Lu

30

Use a indexação combinada com o comprimento de:

echo ${@:${#@}} 

Observe que isso é apenas para o bash.


24
Mais curto:echo ${@:$#}
Pausado até novo aviso.

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

29

O seguinte funcionará para você. O @ é para matriz de argumentos. : significa às. $ # é o comprimento da matriz de argumentos. Portanto, o resultado é o último elemento:

${@:$#} 

Exemplo:

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

Observe que isso é apenas para o bash.


Enquanto o exemplo é para uma função, os scripts também funcionam da mesma maneira. Eu gosto desta resposta porque é clara e concisa.
Jonah Braun

1
E não é hacky. Ele usa recursos explícitos da linguagem, não efeitos colaterais ou qwerks especiais. Essa deve ser a resposta aceita.
musicin3d

25

Foi encontrado ao procurar separar o último argumento de todos os anteriores. Embora algumas das respostas recebam o último argumento, elas não ajudam muito se você precisar de todos os outros argumentos também. Isso funciona muito melhor:

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

Observe que isso é apenas para o bash.


3
A resposta de Steven Penny é um pouco melhor: use ${@: -1}para o último e o segundo${@: -2:1} para o último (e assim por diante ...). Exemplo: bash -c 'echo ${@: -1}' prog 0 1 2 3 4 5 6imprime 6. Para ficar com a abordagem atual do AgileZebra, use ${@:$#-1:1}para obter o segundo último . Exemplo: bash -c 'echo ${@:$#-1:1}' prog 0 1 2 3 4 5 6imprime 5. (e ${@:$#-2:1}para obter o terceiro último e assim por diante ...)
olibre

4
A resposta do AgileZebra fornece uma maneira de obter todos os argumentos, exceto os últimos, para que eu não diga que a resposta de Steven a substitui. No entanto, parece não haver razão $((...))para subtrair o 1, você pode simplesmente usá-lo ${@:1:$# - 1}.
dkasak

Obrigado dkasak. Atualizado para refletir sua simplificação.
AgileZebra

22

Isso funciona em todos os shells compatíveis com POSIX:

eval last=\${$#}

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


2
Veja este comentário anexado a uma resposta idêntica mais antiga.
Pausado até novo aviso.

1
A solução portátil mais simples que vejo aqui. Este não tem nenhum problema de segurança, @DennisWilliamson, a citação parece empiricamente correta, a menos que haja uma maneira de definir $#uma sequência arbitrária (acho que não). eval last=\"\${$#}\"também funciona e está obviamente correto. Não veja por que as aspas não são necessárias.
Palec 17/10

1
Para bash , zsh , dash e ksh eval last=\"\${$#}\" são bons. Mas para csh e tcsh uso eval set last=\"\${$#}\". Veja este exemplo: tcsh -c 'eval set last=\"\${$#}\"; echo "$last"' arg1 arg2 arg3.
olibre

12

Aqui está a minha solução:

  • bastante portátil (todos os POSIX sh, bash, ksh, zsh) devem funcionar
  • não muda argumentos originais (muda uma cópia).
  • não usa o mal eval
  • não repete a lista inteira
  • não usa ferramentas externas

Código:

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

1
Ótima resposta - curta, portátil, segura. Obrigado!
Ján Lalinský

2
Esta é uma ótima idéia, mas tenho algumas sugestões: Primeiro, citar deve ser adicionado ao redor "$1"e "$#"(veja esta ótima resposta unix.stackexchange.com/a/171347 ). Em segundo lugar, echoinfelizmente não é portátil (principalmente para -n), então printf '%s\n' "$1"deve ser usado.
Joki

thanks @joki Eu trabalhei com muitos sistemas unix diferentes e também não confiava echo -n, no entanto, não conheço nenhum sistema posix echo "$1"que falhe. De qualquer forma, printfé realmente mais previsível - atualizado.
Michał Šrajer

@ MichałŠrajer considere o caso em que "$ 1" é "-n" ou "-", portanto, por exemplo, ntharg 1 -nou ntharg 1 --pode gerar resultados diferentes em vários sistemas. O código que você tem agora é seguro!
Joki

10

Das soluções mais antigas às mais recentes:

A solução mais portátil, ainda mais antiga sh(funciona com espaços e caracteres glob) (sem loop, mais rápido):

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

Desde a versão 2.01 do 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 e bash:

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

E para "próximo ao último":

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

Usando printf para solucionar problemas com argumentos que começam com um traço (como -n ).

Para todas as conchas e para as mais antigas sh(funciona com espaços e caracteres glob) é:

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

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

Ou, se você deseja definir um lastvar:

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

E para "próximo ao último":

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



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

Isso muda os argumentos pelo número de argumentos menos 1 e retorna o primeiro (e único) argumento restante, que será o último.

Eu testei apenas no bash, mas deve funcionar em sh e ksh também.


11
shift $(($# - 1))- não há necessidade de um utilitário externo. Funciona em Bash, ksh, zsh e dash.
Pausado até novo aviso.

@Dennis: Legal! Eu não sabia sobre a $((...))sintaxe.
Laurence Gonsalves 11/11

Você deseja usar printf '%s\n' "$1"para evitar comportamentos inesperados de echo(por exemplo, para -n).
Joki #

2

Se você deseja fazer isso de maneira não destrutiva, uma maneira é passar todos os argumentos para uma função e retornar o ú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

Se você realmente não se importa em manter os outros argumentos, não precisa disso em uma função, mas é difícil pensar em uma situação em que você nunca iria querer manter os outros argumentos, a menos que eles já tenham sido processados, nesse caso, eu usaria o método process / shift / process / shift / ... para processá-los sequencialmente.

Estou assumindo aqui que você deseja mantê-los porque não seguiu o método seqüencial. Este método também lida com o caso em que não há argumentos, retornando "". Você pode ajustar esse comportamento facilmente inserindo a elsecláusula comentada .


2

Para tcsh:

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

Tenho certeza de que essa seria uma solução portátil, exceto pela atribuição.


Sei que você postou isso para sempre, mas essa solução é ótima - que bom que alguém postou um tcsh!
user3295674

2

Achei a resposta do @ AgileZebra (mais o comentário do @ starfry) a mais útil, mas ela é headsescalar. Uma matriz é provavelmente mais útil:

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

Observe que isso é apenas para o bash.


Como você converteria as cabeças em uma matriz?
Stephane

Eu já fiz adicionando parênteses, por exemplo, echo "${heads[-1]}"imprime o último elemento heads. Ou eu estou esquecendo de alguma coisa?
EndlosSchleife 11/12/19

1

Uma solução usando eval:

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

echo $last

7
A avaliação para referência indireta é um exagero, para não mencionar práticas inadequadas, e uma grande preocupação de segurança (o valor não é citado em eco ou fora de $ (). O Bash possui uma sintaxe interna para referências indiretas, para qualquer var: !portanto last="${!#}", usaria o mesmo abordagem (referência indireta em $ #) de uma maneira muito mais segura, compacta, embutida e sã. E corretamente citada.
MestreLion

Veja uma maneira mais limpa de executar o mesmo em outra resposta .
Palec 17/10

As cotações do @MestreLion não são necessárias no RHS de =.
Tom Hale

1
@ TomHale: true para o caso particular de ${!#}, mas não em geral: aspas ainda são necessárias se o conteúdo contiver espaço em branco literal, como last='two words'. Somente o $()espaço em branco é seguro, independentemente do conteúdo.
MestreLion 18/06/19

1

Depois de ler as respostas acima, escrevi um shell script de Q&D (deve funcionar com sh e bash) para executar o g ++ no PGM.cpp para produzir a imagem executável PGM. Ele pressupõe que o último argumento na linha de comando seja o nome do arquivo (.cpp é opcional) e todos os outros argumentos são opções.

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

O seguinte será definido LASTpara o último argumento sem alterar o ambiente atual:

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

Se outros argumentos não forem mais necessários e puderem ser alterados, poderá ser simplificado para:

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

Por motivos de portabilidade, a seguir:

shift $(($#-1));

pode ser substituído por:

shift `expr $# - 1`

Substituindo também $()por aspas, obtemos:

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

1
echo $argv[$#argv]

Agora só preciso adicionar um texto, porque minha resposta foi muito curta para postar. Preciso adicionar mais texto para editar.


Em qual shell isso deveria funcionar? Não festança. Não é peixe (tem $argvmas não $#argv- $argv[(count $argv)]funciona em peixe).
Beni Cherniavsky-Paskin

1

Isso faz parte da minha função de cópia:

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

Para usar em scripts, faça o seguinte:

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

Explicação (a maioria aninhada primeiro):

  1. $(echo '$'"$#")retorna $[nr]onde [nr]é o número de parâmetros. Por exemplo, a string $123(não expandida).
  2. echo $123 retorna o valor do 123º parâmetro, quando avaliado.
  3. evalapenas expande $123para o valor do parâmetro, por exemplo last_arg. Isso é interpretado como uma sequência e retornado.

Funciona com o Bash em meados de 2015.


A abordagem de avaliação já foi apresentada aqui muitas vezes, mas esta tem uma explicação de como funciona. Pode ser melhorado ainda mais, mas ainda vale a pena mantê-lo.
Palec

0
#! /bin/sh

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

echo $last

Isso falhará se um argumento for a sequência vazia, mas funcionará em 99,9% dos casos.
Thomas

0

Experimente o script abaixo para encontrar o ú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

Resultado:

 # ./arguments.sh
 No Arguments supplied

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

Obrigado.


Eu suspeito que isso iria falhar com ./arguments.sh "último valor"
Thomas

Obrigado por verificar Thomas, tentei executar o script as como você mencionou # ./arguments.sh "last value" O último argumento é: o valor está funcionando bem agora. # ./arguments.sh "último valor, verifique com o dobro" O último argumento é: double
Ranjithkumar T

O problema é que o último argumento foi 'último valor', e não valor. O erro é causado pelo argumento que contém um espaço.
Thomas

0

Existe uma maneira muito mais concisa de fazer isso. Argumentos para um script bash podem ser trazidos para uma matriz, o que torna muito mais fácil lidar com os elementos. O script abaixo sempre imprimirá o último argumento passado para um 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]}

Saída de amostra

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

0

Usando expansão de parâmetro (excluir início correspondente):

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

Também é fácil obter tudo antes do último:

prelast=${args% *}

Possível duplicação (ruim) de stackoverflow.com/a/37601842/1446229
Xerz

Isso funciona apenas se o último argumento não contiver um espaço.
Palec 18/03/19

Sua pré-impressão falhará se o último argumento for uma frase entre aspas duplas, com a frase sendo supostamente um argumento.
Stephane

0

Para retornar o último argumento do comando usado mais recentemente, use o parâmetro especial:

$_

Nesse caso, ele funcionará se for usado dentro do script antes que outro comando seja chamado.


Não é isso que a pergunta está fazendo
retnikt

-1

Apenas use !$.

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

Não é isso que a pergunta está fazendo
retnikt
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.