Получение последнего аргумента, переданного в сценарий оболочки


272

$1это первый аргумент.
$@это все из них.

Как я могу найти последний аргумент, переданный сценарию оболочки?


Я использовал bash, но чем более портативное решение, тем лучше.
Томас


10
использовать можно также использовать $ {! #}
Prateek Joshi

11
Только для Баша , в ответ Кевин Литтл предлагает простой ${!#}. Проверьте это с помощью bash -c 'echo ${!#}' arg1 arg2 arg3. Для Баш , KSH и Zsh , в ответ Деннис Уильямсона предлагает ${@: -1}. Кроме того, ${*: -1}также может быть использован. Проверьте это с помощью zsh -c 'echo ${*: -1}' arg1 arg2 arg3. Но это не работает для dash , csh и tcsh .
olibre

2
${!#}В отличие ${@: -1}, также работает с расширением параметра. Вы можете проверить это с bash -c 'echo ${!#%.*}' arg1.out arg2.out arg3.out.
Arch Stanton

Ответы:


177

Это что-то вроде хака:

for last; do true; done
echo $last

Этот также довольно переносим (опять же, должен работать с bash, ksh и sh) и не сдвигает аргументы, что может быть хорошо.

Он использует тот факт, что forнеявно зацикливается на аргументах, если вы не указываете, что зацикливать, и тот факт, что переменные цикла не ограничены: они сохраняют последнее значение, которое им было задано.


10
@ Михал Шрайер, я думаю, что вы имели в виду двоеточие, а не запятую;)
Павел Надольский


4
В старом Solaris со старой оболочкой Bourne (не POSIX) я должен написать "напоследок" в $ @; do true; done "
mcoolive

8
@mcoolive @LaurenceGolsalves помимо того, что является более портативным, for last in "$@"; do :; doneтакже делает цель намного более ясной.
Адриан Гюнтер

1
@mcoolive: он даже работает в оболочке unix v7 bourne с 1979 года. Вы не сможете получить больше портативности, если он работает как на v7 sh, так и на POSIX sh :)
Dominik R

291

Это только для Bash:

echo "${@: -1}"

43
Для тех (как я), которые задаются вопросом, зачем нужно место, man bash может сказать следующее:> Обратите внимание, что отрицательное смещение должно быть отделено от двоеточия хотя бы одним пробелом, чтобы не перепутать с расширением: -.
Foo

1
Я использовал это, и он ломается в MSYS2 Bash только в Windows. Bizarre.
Стивен Лу

2
Примечание: этот ответ работает для всех массивов Bash, в отличие от того, ${@:$#}который работает только на $@. Если вы хотите скопировать $@в новый массив с arr=("$@"), ${arr[@]:$#}будет неопределенным. Это потому, что $@имеет 0-й элемент, который не включен в "$@"расширения.
Мистер Лама

@ Mr.Llama: Еще одно место, которого следует избегать, $#- это перебирать массивы, так как в Bash массивы редки, и хотя в нем $#будет показано количество элементов в массиве, он не обязательно указывает на последний элемент (или элемент + 1). Другими словами, не следует делать for ((i = 0; i++; i < $#)); do something "${array[$i]}"; doneи вместо этого делать for element in "${array[@]}"; do something "$element"; doneили перебирать индексы: for index in "${!array[@]}"; do something "$index" "${array[$index]}"; doneесли вам нужно что-то делать со значениями индексов.
Приостановлено до дальнейшего уведомления.

80
$ set quick brown fox jumps

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

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

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

Пространство необходимо, чтобы оно не интерпретировалось как значение по умолчанию .

Обратите внимание, что это только для bash.


9
Лучший ответ, так как он также включает в себя следующий за последним аргумент. Спасибо!
с40

1
Стивен, я не знаю, что ты сделал, чтобы попасть в штрафную, но мне нравится твоя работа здесь.
Бруно Броноски

4
да. просто лучший. все, кроме командования и последнего аргумента ${@: 1:$#-1}
Dyno Fu

1
@DynoFu спасибо за это, вы ответили на мой следующий вопрос. Таким образом, сдвиг может выглядеть следующим образом:, echo ${@: -1} ${@: 1:$#-1}где последний становится первым, а остальные сползают вниз
Майк

73

Самый простой ответ для Bash 3.0 или выше:

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

Вот и все.

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

Выход:

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

1
$BASH_ARGVне работает внутри функции bash (если я не делаю что-то не так).
Big McLargeHuge

1
BASH_ARGV имеет аргументы при вызове bash (или функции), а не текущий список позиционных аргументов.
Исаак

Также обратите внимание, что BASH_ARGVвы получите значение, которое было последним аргументом, а не просто «последнее значение». Например !: если вы предоставите один единственный аргумент, то вы вызовете shift, ${@:$#}ничего не произойдет (потому что вы переместили один-единственный аргумент!), Однако, BASH_ARGVвы все равно получите этот (ранее) последний аргумент.
Стивен Лу


29

Следующее будет работать для вас. @ Для массива аргументов. : означает в. $ # - длина массива аргументов. Итак, результатом является последний элемент:

${@:$#} 

Пример:

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

Обратите внимание, что это только для bash.


Хотя пример для функции, сценарии также работают аналогичным образом. Мне нравится этот ответ, потому что он ясен и лаконичен.
Иона Браун

1
И это не хаки. Он использует явные особенности языка, а не побочные эффекты или специальные qwerks. Это должен быть принятый ответ.
musicin3d

25

Нашел это, когда пытался отделить последний аргумент от всех предыдущих. Хотя некоторые ответы получают последний аргумент, они не сильно помогают, если вам нужны все остальные аргументы. Это работает намного лучше:

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

Обратите внимание, что это только для bash.


3
В ответ Стивена Пенни немного лучше: использовать ${@: -1}для последней и ${@: -2:1}для второй последний (и так далее ...). Пример: bash -c 'echo ${@: -1}' prog 0 1 2 3 4 5 6печать 6. Чтобы остаться с этим текущим подходом AgileZebra, используйте, ${@:$#-1:1}чтобы получить второй последний . Пример: bash -c 'echo ${@:$#-1:1}' prog 0 1 2 3 4 5 6печать 5. (и ${@:$#-2:1}получить третий последний и так далее ...)
olibre

4
Ответ AgileZebra предоставляет способ получить все, кроме последних аргументов, поэтому я бы не сказал, что ответ Стивена заменяет его. Однако нет смысла использовать $((...))для вычитания 1, вы можете просто использовать ${@:1:$# - 1}.
dkasak

Спасибо dkasak. Обновлено, чтобы отразить ваше упрощение.
AgileZebra

22

Это работает во всех POSIX-совместимых оболочках:

eval last=\${$#}

Источник: http://www.faqs.org/faqs/unix-faq/faq/part2/section-12.html.


2
Смотрите этот комментарий, прикрепленный к старому идентичному ответу.
Приостановлено до дальнейшего уведомления.

1
Самое простое портативное решение, которое я вижу здесь. В этом нет проблем с безопасностью, @DennisWilliamson, эмпирически цитирование кажется правильным, если только нет способа установить $#произвольную строку (я так не думаю). eval last=\"\${$#}\"также работает и, очевидно, правильно. Не понимаю, почему цитаты не нужны.
Палек

1
Для bash , zsh , dash и ksh eval last=\"\${$#}\" это хорошо. Но для CSH и Tcsh использования eval set last=\"\${$#}\". Смотрите этот пример tcsh -c 'eval set last=\"\${$#}\"; echo "$last"' arg1 arg2 arg3.
olibre

12

Вот мое решение:

  • довольно портативный (все POSIX sh, bash, ksh, zsh) должны работать
  • не сдвигает оригинальные аргументы (сдвигает копию).
  • не использует зло eval
  • не перебирает весь список
  • не использует внешние инструменты

Код:

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

1
Отличный ответ - короткий, портативный, безопасный. Спасибо!
Ян Лалинский

2
Это отличная идея, но у меня есть несколько предложений: Во - первых , цитирую должны быть добавлены как вокруг "$1"и "$#"(см это большой ответ unix.stackexchange.com/a/171347 ). Во-вторых, echoэто, к сожалению, непереносимо (особенно для -n), поэтому printf '%s\n' "$1"следует использовать вместо.
Joki

Благодаря @joki, я работал со многими различными Unix-системами, и я бы тоже не стал доверять echo -n, однако я не знаю ни одной системы Posix, где echo "$1"бы произошел сбой. Во всяком случае, printfдействительно более предсказуемо - обновлено.
Михал Шрайер

@ MichałŠrajer рассмотрим случай, когда «$ 1» равен «-n» или «-», например, ntharg 1 -nили ntharg 1 --может давать разные результаты в разных системах. Код, который у вас есть сейчас, безопасен!
Джоки

10

От самых старых до новых решений:

Наиболее переносимое решение, даже более старое sh(работает с пробелами и символами глобуса) (без цикла, быстрее):

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

Начиная с версии 2.01 Bash

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

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

Для ksh, zsh и bash:

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

И для «рядом с последним»:

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

Использование printf для обхода любых проблем с аргументами, начинающимися с тире (например -n).

Для всех оболочек и для старых sh(работает с пробелами и символами глобуса) это:

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

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

Или, если вы хотите установить lastпеременную:

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

И для «рядом с последним»:

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



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

Это сдвигает аргументы на количество аргументов минус 1 и возвращает первый (и единственный) оставшийся аргумент, который будет последним.

Я тестировал только в bash, но он также должен работать в sh и ksh.


11
shift $(($# - 1))- нет необходимости во внешней утилите. Работает в Bash, Ksh, Zsh и Dash.
Приостановлено до дальнейшего уведомления.

@ Денис: Хорошо! Я не знал о $((...))синтаксисе.
Лоуренс Гонсалвес

Вы хотели бы использовать printf '%s\n' "$1", чтобы избежать неожиданного поведения echo(например, для -n).
Joki

2

Если вы хотите сделать это неразрушающим способом, один из способов - передать все аргументы функции и вернуть последний:

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

Если вам на самом деле все равно о сохранении других аргументов, вам это не нужно в функции, но мне трудно думать о ситуации, когда вы никогда не захотите сохранять другие аргументы, если они не были уже обработаны, в этом случае я бы использовал метод process / shift / process / shift / ... для их последовательной обработки.

Я предполагаю, что вы хотите сохранить их, потому что вы не следовали последовательному методу. Этот метод также обрабатывает случай, когда нет аргументов, возвращая "". Вы можете легко настроить это поведение, вставив закомментированное elseпредложение.


2

Для tcsh:

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

Я вполне уверен, что это было бы портативное решение, за исключением задания.


Я знаю, что вы опубликовали это навсегда, но это отличное решение - рад, что кто-то опубликовал tcsh!
user3295674

2

Я нашел ответ @ AgileZebra (плюс комментарий @ starfry) наиболее полезным, но он задает headsскаляр. Массив, вероятно, более полезен:

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

Обратите внимание, что это только для bash.


Как бы вы преобразовали головы в массив?
Стефан

Я уже сделал, добавив скобки, например, echo "${heads[-1]}"печатает последний элемент в heads. Или я что-то упустил?
EndlosSchleife

1

Решение с использованием eval:

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

echo $last

7
eval для косвенных ссылок - это излишество, не говоря уже о плохой практике, и огромная проблема безопасности (значение не заключено в кавычки в echo или вне $ (). Bash имеет встроенный синтаксис для косвенных ссылок, для любой переменной var: !поэтому last="${!#}"будет использовать тот же подход (косвенная ссылка на $ #) гораздо более безопасным, компактным, встроенным, вменяемым образом и правильно процитированным
MestreLion

Смотрите более чистый способ сделать то же самое в другом ответе .
Palec

Кавычки @MestreLion не нужны на RHS =.
Том Хейл

1
@TomHale: true для частного случая ${!#}, но не в целом: кавычки по-прежнему необходимы, если содержимое содержит буквенные пробелы, например last='two words'. Только $()безопасные пробелы независимо от содержания.
МестреЛион

1

После прочтения ответов выше я написал сценарий оболочки Q & D (должен работать на sh и bash) для запуска g ++ на PGM.cpp для создания исполняемого образа PGM. Предполагается, что последним аргументом в командной строке является имя файла (.cpp является необязательным), а все остальные аргументы являются опциями.

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

Следующее будет устанавливать LASTпоследний аргумент без изменения текущей среды:

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

Если другие аргументы больше не нужны и их можно сместить, их можно упростить до:

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

По причинам переносимости следующее:

shift $(($#-1));

можно заменить на:

shift `expr $# - 1`

Заменив также $()обратными кавычками, мы получим:

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

1
echo $argv[$#argv]

Теперь мне просто нужно добавить текст, потому что мой ответ был слишком коротким для публикации. Мне нужно добавить больше текста для редактирования.


В какой оболочке предполагается работать? Не баш Не рыба (есть, $argvно нет $#argv- $argv[(count $argv)]работает в рыбе).
Бени Чернявский-Паскин

1

Это часть моей функции копирования:

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

Чтобы использовать в скриптах, сделайте это:

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

Пояснение (сначала вложенное):

  1. $(echo '$'"$#")возвращает $[nr]где [nr]число параметров. Например, строка $123(нерасширенная).
  2. echo $123 возвращает значение 123-го параметра при оценке.
  3. evalпросто расширяется $123до значения параметра, например last_arg. Это интерпретируется как строка и возвращается.

Работает с Bash с середины 2015 года.


Подход eval был представлен здесь уже много раз, но у этого есть объяснение того, как он работает. Может быть улучшено, но все же стоит сохранить.
Палек

0
#! /bin/sh

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

echo $last

Это не удастся, если аргумент является пустой строкой, но будет работать в 99,9% случаев.
Томас

0

Попробуйте приведенный ниже скрипт, чтобы найти последний аргумент

 # 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

Вывод:

 # ./arguments.sh
 No Arguments supplied

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

Спасибо.


Я подозреваю, что это потерпит неудачу с ./arguments.sh "last value"
Томас

Спасибо за проверку, Томас, я пытался выполнить сценарий as, как вы подразумевали # ./arguments.sh "last value" Последний аргумент: значение работает нормально. # ./arguments.sh "проверка последнего значения с двойным" Последний аргумент: double
Ranjithkumar T

Проблема в том, что последний аргумент был «последним значением», а не значением. Ошибка вызвана аргументом, содержащим пробел.
Томас

0

Существует гораздо более краткий способ сделать это. Аргументы скрипта bash могут быть перенесены в массив, что значительно упрощает работу с элементами. Скрипт ниже всегда будет печатать последний аргумент, переданный скрипту.

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

Образец вывода

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

0

Используя расширение параметра (удалить соответствующее начало):

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

Также легко получить все до последнего:

prelast=${args% *}

Возможный (плохой) дубликат stackoverflow.com/a/37601842/1446229
Xerz

Это работает, только если последний аргумент не содержит пробела.
Palec

Ваша предварительная проверка завершается неудачно, если последний аргумент - это предложение, заключенное в двойные кавычки, причем указанное предложение должно быть одним аргументом.
Стефан

0

Чтобы вернуть последний аргумент последней использованной команды, используйте специальный параметр:

$_

В этом случае он будет работать, если он используется в сценарии до вызова другой команды.


Это не то, что задает вопрос
retnikt

-1

Просто используйте !$.

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

Это не то, что задает вопрос
retnikt
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.