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

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
@ Михал Шрайер, я думаю, что вы имели в виду двоеточие, а не запятую;)
Павел Надольский
2
@ MichałŠrajer trueявляется частью POSIX .
Rufflewind
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вы все равно получите этот (ранее) последний аргумент.
Стивен Лу
30

Используйте индексирование в сочетании с длиной:

echo ${@:${#@}} 

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

Марк Байерс
источник
24
Короче:echo ${@:$#}
Приостановлено до дальнейшего уведомления.
1
echo $ (@ & $; & ^ $ &% @ ^! @% ^ # ** # & @ * # @ * # @ (& * # _ ** ^ @ ^ & (^ @ & * & @)
Атул
29

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

${@:$#} 

Пример:

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

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

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

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

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

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

AgileZebra
источник
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.

poiuz
источник
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
Исаак
источник
8

Если вы используете Bash> = 3.0

echo ${BASH_ARGV[0]}
Душан
источник
Также для следующего к последнему аргументу, сделайтеecho ${BASH_ARGV[1]}
Стивен Пенни
1
ARGVрасшифровывается как «вектор аргумента».
Серж Строобандт,
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предложение.

paxdiablo
источник
2

Для tcsh:

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

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

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

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

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

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

EndlosSchleife
источник
Как бы вы преобразовали головы в массив?
Стефан
Я уже сделал, добавив скобки, например, 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]

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

frank1rizzo
источник
В какой оболочке предполагается работать? Не баш Не рыба (есть, $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 года.

Esavier
источник
Подход 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
tekbot
источник
0

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

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

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

prelast=${args% *}
Jurij
источник
Возможный (плохой) дубликат stackoverflow.com/a/37601842/1446229
Xerz
Это работает, только если последний аргумент не содержит пробела.
Palec
Ваша предварительная проверка завершается неудачно, если последний аргумент - это предложение, заключенное в двойные кавычки, причем указанное предложение должно быть одним аргументом.
Стефан
0

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

$_

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

Тейн
источник
Это не то, что задает вопрос
retnikt
-1

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

$ mkdir folder
$ cd !$ # will run: cd folder
r1oga
источник
Это не то, что задает вопрос
retnikt