Obtention du dernier argument passé à un script shell


272

$1est le premier argument.
$@est tous.

Comment trouver le dernier argument passé à un script shell?


J'utilisais bash, mais plus la solution est portable, mieux c'est.
Thomas


10
utilisation peut également utiliser $ {! #}
Prateek Joshi

11
Pour seulement bash , la réponse de Kevin Little propose le simple ${!#}. Testez-le en utilisant bash -c 'echo ${!#}' arg1 arg2 arg3. Pour bash , ksh et zsh , la réponse de Dennis Williamson propose ${@: -1}. De plus ${*: -1}peut également être utilisé. Testez-le en utilisant zsh -c 'echo ${*: -1}' arg1 arg2 arg3. Mais cela ne fonctionne pas pour dash , csh et tcsh .
olibre

2
${!#}, contrairement à ${@: -1}, fonctionne également avec l'expansion des paramètres. Vous pouvez le tester avec bash -c 'echo ${!#%.*}' arg1.out arg2.out arg3.out.
Arch Stanton

Réponses:


177

C'est un peu un hack:

for last; do true; done
echo $last

Celui-ci est également assez portable (encore une fois, devrait fonctionner avec bash, ksh et sh) et il ne modifie pas les arguments, ce qui pourrait être agréable.

Il utilise le fait que les forboucles sont implicitement répétées si vous ne lui dites pas quoi boucler, et le fait que les variables de boucle ne sont pas délimitées: elles conservent la dernière valeur à laquelle elles ont été définies.


10
@ MichałŠrajer, je pense que vous vouliez dire deux points et non une virgule;)
Paweł Nadolski

2
@ MichałŠrajer truefait partie de POSIX .
Rufflewind

4
Avec un vieux Solaris, avec le vieux shell bourne (pas POSIX), je dois écrire "pour la fin dans" $ @ "; faire vrai; fait"
mcoolive

8
@mcoolive @LaurenceGolsalves en plus d'être plus portable, for last in "$@"; do :; donerend également l'intention beaucoup plus claire.
Adrian Günter

1
@mcoolive: il fonctionne même dans le shell bourne unix v7 de 1979. vous ne pouvez pas être plus portable s'il fonctionne à la fois sur v7 sh et POSIX sh :)
Dominik R

291

C'est uniquement Bash:

echo "${@: -1}"

43
Pour ceux (comme moi) qui se demandent pourquoi l'espace est nécessaire, man bash a ceci à dire à ce sujet:> Notez qu'un décalage négatif doit être séparé du colon par au moins un espace pour éviter d'être confondu avec: - l'expansion.
foo

1
J'ai utilisé ceci et il se casse dans bash MSYS2 dans les fenêtres uniquement. Bizarre.
Steven Lu

2
Remarque: Cette réponse fonctionne pour tous les tableaux Bash, contrairement à ${@:$#}ce qui ne fonctionne que sur $@. Si vous deviez copier $@dans un nouveau tableau avec arr=("$@"), ${arr[@]:$#}ne serait pas défini. En effet, $@a un 0ème élément qui n'est pas inclus dans les "$@"extensions.
M. Llama

@ Mr.Llama: Un autre endroit à éviter $#est lors de l'itération sur les tableaux car, dans Bash, les tableaux sont clairsemés et tandis que $#montrera le nombre d'éléments dans le tableau, il ne pointe pas nécessairement vers le dernier élément (ou élément + 1). En d'autres termes, il ne faut pas faire for ((i = 0; i++; i < $#)); do something "${array[$i]}"; doneet plutôt faire for element in "${array[@]}"; do something "$element"; doneou itérer sur les indices: for index in "${!array[@]}"; do something "$index" "${array[$index]}"; donesi vous avez besoin de faire quelque chose avec les valeurs des indices.
pause jusqu'à nouvel ordre.

80
$ set quick brown fox jumps

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

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

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

L'espace est nécessaire pour qu'il ne soit pas interprété comme une valeur par défaut .

Notez que c'est uniquement bash.


9
Meilleure réponse, car elle comprend également l'avant-dernier argument. Merci!
e40

1
Steven, je ne sais pas ce que tu as fait pour atterrir dans la Penalty Box, mais j'aime ton travail ici.
Bruno Bronosky

4
Oui. simplement le meilleur. tout sauf commande et dernier argument ${@: 1:$#-1}
Dyno Fu

1
@DynoFu merci pour cela, vous avez répondu à ma prochaine question. Ainsi, un changement pourrait ressembler à:, echo ${@: -1} ${@: 1:$#-1}où le dernier devient premier et le reste glisse vers le bas
Mike

73

La réponse la plus simple pour bash 3.0 ou supérieur est

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

C'est tout.

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

La sortie est:

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

1
$BASH_ARGVne fonctionne pas dans une fonction bash (sauf si je fais quelque chose de mal).
Big McLargeHuge

1
Le BASH_ARGV a les arguments quand bash a été appelé (ou à une fonction) pas la liste actuelle des arguments positionnels.
Isaac

Notez également que ce BASH_ARGVqui vous donnera est la valeur que le dernier argument donné a été, au lieu de simplement "la dernière valeur". Par exemple!: Si vous fournissez un seul argument, alors vous appelez shift, ${@:$#}ne produira rien (car vous avez déplacé le seul et unique argument!), Cependant, BASH_ARGVvous donnera toujours ce (anciennement) dernier argument.
Steven Lu

30

Utilisez l'indexation combinée à la longueur de:

echo ${@:${#@}} 

Notez que c'est uniquement bash.


24
Plus court:echo ${@:$#}
pause jusqu'à nouvel ordre.

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

29

Ce qui suit fonctionnera pour vous. Le @ est pour un tableau d'arguments. : signifie à. $ # est la longueur du tableau d'arguments. Le résultat est donc le dernier élément:

${@:$#} 

Exemple:

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

Notez que c'est uniquement bash.


Bien que l'exemple concerne une fonction, les scripts fonctionnent également de la même manière. J'aime cette réponse car elle est claire et concise.
Jonah Braun

1
Et ce n'est pas hacky. Il utilise des fonctionnalités explicites de la langue, pas des effets secondaires ou des qwerks spéciaux. Ce devrait être la réponse acceptée.
musicin3d

25

Trouvé ceci en cherchant à séparer le dernier argument de tous les précédents. Bien que certaines des réponses obtiennent le dernier argument, elles ne sont pas très utiles si vous avez également besoin de tous les autres arguments. Cela fonctionne beaucoup mieux:

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

Notez que c'est uniquement bash.


3
La réponse de Steven Penny est un peu plus agréable: utilisez ${@: -1}pour la dernière et ${@: -2:1}pour l' avant-dernière (et ainsi de suite ...). Exemple: bash -c 'echo ${@: -1}' prog 0 1 2 3 4 5 6impressions 6. Pour rester avec l'approche actuelle d'AgileZebra, utilisez ${@:$#-1:1}pour obtenir l' avant-dernier . Exemple: bash -c 'echo ${@:$#-1:1}' prog 0 1 2 3 4 5 6impressions 5. (et ${@:$#-2:1}pour obtenir l'avant- dernier et ainsi de suite ...)
olibre

4
La réponse d'AgileZebra fournit un moyen d'obtenir tout sauf les derniers arguments, donc je ne dirais pas que la réponse de Steven la remplace. Cependant, il ne semble pas y avoir de raison d'utiliser $((...))pour soustraire le 1, vous pouvez simplement utiliser ${@:1:$# - 1}.
dkasak

Merci dkasak. Mis à jour pour refléter votre simplification.
AgileZebra

22

Cela fonctionne dans tous les shells compatibles POSIX:

eval last=\${$#}

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


2
Voir ce commentaire joint à une réponse identique plus ancienne.
pause jusqu'à nouvel ordre.

1
La solution portable la plus simple que je vois ici. Celui-ci n'a aucun problème de sécurité, @DennisWilliamson, la citation semble empiriquement être bien faite, à moins qu'il n'y ait un moyen de définir $#une chaîne arbitraire (je ne pense pas). eval last=\"\${$#}\"fonctionne également et est évidemment correct. Je ne vois pas pourquoi les citations ne sont pas nécessaires.
Palec

1
Pour bash , zsh , dash et ksh, eval last=\"\${$#}\" c'est bien. Mais pour csh et tcsh utilisation eval set last=\"\${$#}\". Voir cet exemple: tcsh -c 'eval set last=\"\${$#}\"; echo "$last"' arg1 arg2 arg3.
olibre

12

Voici ma solution:

  • assez portable (tous les POSIX sh, bash, ksh, zsh) devraient fonctionner
  • ne décale pas les arguments d'origine (décale une copie).
  • n'utilise pas le mal eval
  • ne parcourt pas toute la liste
  • n'utilise pas d'outils externes

Code:

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

1
Grande réponse - courte, portable, sûre. Merci!
Ján Lalinský

2
C'est une excellente idée, mais j'ai quelques suggestions: Tout d'abord, les citations doivent être ajoutées à la fois autour "$1"et "$#"(voir cette excellente réponse unix.stackexchange.com/a/171347 ). Deuxièmement, echoest malheureusement non portable (en particulier pour -n), printf '%s\n' "$1"doit donc être utilisé à la place.
joki

merci @joki J'ai travaillé avec de nombreux systèmes Unix différents et je ne ferais pas confiance non echo -nplus, mais je ne suis au courant d'aucun système Posix où echo "$1"cela échouerait. Quoi qu'il en soit, printfest en effet plus prévisible - mis à jour.
Michał Šrajer

@ MichałŠrajer considère le cas où "$ 1" est "-n" ou "-", par exemple ntharg 1 -nou ntharg 1 --peut donner des résultats différents sur différents systèmes. Le code que vous avez maintenant est sûr!
joki

10

Des solutions les plus anciennes aux plus récentes:

La solution la plus portable, encore plus ancienne sh(fonctionne avec des espaces et des caractères globaux) (pas de boucle, plus rapide):

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

Depuis la version 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

Pour ksh, zsh et bash:

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

Et pour "l'avant-dernier":

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

Utilisation de printf pour contourner tous les problèmes avec des arguments qui commencent par un tiret (comme -n).

Pour tous les shells et pour les plus anciens sh(fonctionne avec des espaces et des caractères globaux) est:

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

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

Ou, si vous souhaitez définir un lastvar:

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

Et pour "l'avant-dernier":

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



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

Cela décale les arguments du nombre d'arguments moins 1 et renvoie le premier (et le seul) argument restant, qui sera le dernier.

J'ai seulement testé en bash, mais cela devrait aussi fonctionner en sh et ksh.


11
shift $(($# - 1))- pas besoin d'un utilitaire externe. Fonctionne en Bash, ksh, zsh et dash.
pause jusqu'à nouvel ordre.

@Dennis: Nice! Je ne connaissais pas la $((...))syntaxe.
Laurence Gonsalves

Vous souhaitez utiliser printf '%s\n' "$1"afin d'éviter un comportement inattendu de echo(par exemple pour -n).
joki

2

Si vous voulez le faire de manière non destructive, une façon consiste à passer tous les arguments à une fonction et à renvoyer le dernier:

#!/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 vous ne vous souciez pas vraiment de conserver les autres arguments, vous n'en avez pas besoin dans une fonction, mais j'ai du mal à penser à une situation où vous ne voudriez jamais conserver les autres arguments à moins qu'ils n'aient déjà été traités, auquel cas j'utiliserais la méthode process / shift / process / shift / ... pour les traiter séquentiellement.

Je suppose ici que vous souhaitez les conserver car vous n'avez pas suivi la méthode séquentielle. Cette méthode gère également le cas où il n'y a pas d'arguments, en retournant "". Vous pouvez facilement ajuster ce comportement en insérant la elseclause commentée .


2

Pour tcsh:

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

Je suis tout à fait sûr que ce serait une solution portable, à l'exception de l'affectation.


Je sais que vous avez posté cela il y a toujours, mais cette solution est excellente - content que quelqu'un en ait posté une sur tcsh!
user3295674

2

J'ai trouvé la réponse de @ AgileZebra (plus le commentaire de @ starfry) la plus utile, mais elle prend headsun scalaire. Un tableau est probablement plus utile:

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

Notez que c'est uniquement bash.


Comment convertiriez-vous les têtes en un tableau?
Stéphane

Je l'ai déjà fait en ajoutant les parenthèses, par exemple, echo "${heads[-1]}"imprime le dernier élément dans heads. Ou est-ce que je manque quelque chose?
EndlosSchleife

1

Une solution utilisant eval:

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

echo $last

7
eval pour la référence indirecte est exagéré, sans parler des mauvaises pratiques, et un énorme problème de sécurité (la valeur n'est pas citée en écho ou en dehors de $ (). Bash a une syntaxe intégrée pour les références indirectes, pour tout var: !donc last="${!#}"utiliserait le même approche (référence indirecte sur $ #) d'une manière beaucoup plus sûre, compacte, intégrée, saine et correctement citée
MestreLion

Voir une façon plus propre de faire la même chose dans une autre réponse .
Palec

Les citations @MestreLion ne sont pas nécessaires sur le RHS de =.
Tom Hale

1
@TomHale: vrai pour le cas particulier de ${!#}, mais pas en général: des guillemets sont toujours nécessaires si le contenu contient des espaces littéraux, tels que last='two words'. Seul l' $()espace blanc est protégé quel que soit le contenu.
MestreLion

1

Après avoir lu les réponses ci-dessus, j'ai écrit un script shell Q&D (devrait fonctionner sur sh et bash) pour exécuter g ++ sur PGM.cpp pour produire une image exécutable PGM. Il suppose que le dernier argument de la ligne de commande est le nom du fichier (.cpp est facultatif) et tous les autres arguments sont des options.

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

Ce qui suit prendra le LASTdernier argument sans changer l'environnement actuel:

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

Si d'autres arguments ne sont plus nécessaires et peuvent être déplacés, il peut être simplifié pour:

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

Pour des raisons de portabilité suivantes:

shift $(($#-1));

peut être remplacé par:

shift `expr $# - 1`

Remplaçant également $()par des guillemets, nous obtenons:

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

1
echo $argv[$#argv]

Maintenant, j'ai juste besoin d'ajouter du texte parce que ma réponse était trop courte pour être publiée. J'ai besoin d'ajouter plus de texte à modifier.


Dans quelle coquille est-ce censé fonctionner? Pas bash. Pas de poisson (a $argvmais pas $#argv- $argv[(count $argv)]fonctionne dans le poisson).
Beni Cherniavsky-Paskin

1

Cela fait partie de ma fonction de copie:

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

Pour l'utiliser dans des scripts, procédez comme suit:

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

Explication (la plus imbriquée en premier):

  1. $(echo '$'"$#")renvoie $[nr][nr]est le nombre de paramètres. Par exemple, la chaîne $123(non développée).
  2. echo $123 renvoie la valeur du 123ème paramètre, lorsqu'elle est évaluée.
  3. evalse développe simplement $123à la valeur du paramètre, par exemple last_arg. Ceci est interprété comme une chaîne et renvoyé.

Fonctionne avec Bash à partir de la mi-2015.


L'approche eval a déjà été présentée ici plusieurs fois, mais celle-ci explique comment elle fonctionne. Pourrait être encore amélioré, mais mérite d'être conservé.
Palec

0
#! /bin/sh

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

echo $last

Cela échouera si un argument est la chaîne vide, mais fonctionnera dans 99,9% des cas.
Thomas

0

Essayez le script ci-dessous pour trouver le dernier argument

 # 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

Production:

 # ./arguments.sh
 No Arguments supplied

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

Merci.


Je soupçonne que cela échouerait avec ./arguments.sh "dernière valeur"
Thomas

Merci d'avoir vérifié Thomas, j'ai essayé d'exécuter le script as comme vous avez mentinoé # ./arguments.sh "dernière valeur" Le dernier argument est: la valeur fonctionne bien maintenant. # ./arguments.sh "dernière vérification de valeur avec double" Le dernier argument est: double
Ranjithkumar T

Le problème est que le dernier argument était «dernière valeur» et non valeur. L'erreur est provoquée par l'argument contenant un espace.
Thomas

0

Il existe une manière beaucoup plus concise de procéder. Les arguments d'un script bash peuvent être introduits dans un tableau, ce qui rend le traitement des éléments beaucoup plus simple. Le script ci-dessous imprimera toujours le dernier argument passé à 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]}

Exemple de sortie

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

0

Utilisation de l'expansion des paramètres (supprimer le début correspondant):

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

Il est également facile d'obtenir tout avant-dernier:

prelast=${args% *}

Copie (pauvre) possible de stackoverflow.com/a/37601842/1446229
Xerz

Cela ne fonctionne que si le dernier argument ne contient pas d'espace.
Palec

Votre prélast échoue si le dernier argument est une phrase entre guillemets doubles, ladite phrase étant supposée être un argument.
Stéphane

0

Pour renvoyer le dernier argument de la dernière commande utilisée, utilisez le paramètre spécial:

$_

Dans ce cas, cela fonctionnera s'il est utilisé dans le script avant qu'une autre commande ait été invoquée.


Ce n'est pas ce que la question demande
retnikt

-1

Utilisez simplement !$.

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

Ce n'est pas ce que la question demande
retnikt
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.