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?

Thomas
fonte
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.

Laurence Gonsalves
fonte
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}"
Pausado até novo aviso.
fonte
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.

Steven Penny
fonte
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
Kevin Little
fonte
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.

Mark Byers
fonte
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.

Tahsin Turkoz
fonte
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.

AgileZebra
fonte
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

poiuz
fonte
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 $# "$@"`
Michał Šrajer
fonte
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
Isaac
fonte
8

Se você estiver usando o Bash> = 3.0

echo ${BASH_ARGV[0]}
Dusan
fonte
Também para o penúltimo argumento, façaecho ${BASH_ARGV[1]}
Steven Penny
1
ARGVsignifica "vetor de argumento".
Serge Stroobandt
5

Pois bash, este comentário sugeriu o muito elegante:

echo "${@:$#}"

Como um bônus, isso também funciona zsh.

Tom Hale
fonte
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.

Laurence Gonsalves
fonte
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 .

paxdiablo
fonte
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.

Perfect64
fonte
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.

EndlosSchleife
fonte
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
Mikael S
fonte
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
David E.
fonte
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
Paweł Nadolski
fonte
1
echo $argv[$#argv]

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

frank1rizzo
fonte
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.

Esavier
fonte
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
Craig Trader
fonte
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.

Ranjithkumar T
fonte
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
tekbot
fonte
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% *}
Jurij
fonte
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.

Thain
fonte
Não é isso que a pergunta está fazendo
retnikt
-1

Apenas use !$.

$ mkdir folder
$ cd !$ # will run: cd folder
r1oga
fonte
Não é isso que a pergunta está fazendo
retnikt