Отримання останнього аргументу передається сценарію оболонки


272

$1є першим аргументом.
$@це всі вони.

Як я можу знайти останній аргумент, переданий сценарію оболонки?


Я використовував bash, але чим портативніше рішення, тим краще.
— Томас


10
також можна використовувати $ {! #}
— Prateek Joshi

11
Що стосується лише баш , відповідь Кевіна Літта пропонує просте ${!#}. Перевірте це за допомогою bash -c 'echo ${!#}' arg1 arg2 arg3. Що стосується bash , ksh та zsh , то пропонується відповідь Денніса Вільямсона${@: -1} . Крім того, ${*: -1}також можна використовувати. Перевірте це за допомогою zsh -c 'echo ${*: -1}' arg1 arg2 arg3. Але це не працює для тире , csh і tcsh .
— олібре

2
${!#}на відміну ${@: -1}, також працює з розширенням параметрів. Ви можете протестувати bash -c 'echo ${!#%.*}' arg1.out arg2.out arg3.out.
— Арк Стентон

Відповіді:


177

Це трохи хак:

for last; do true; done
echo $last

Цей також досить портативний (знову ж, він повинен працювати з bash, ksh та sh), і він не змінює аргументи, що може бути добре.

Він використовує той факт, що forнеявно перетворює циркуляцію на аргументи, якщо ви не вказуєте, на що робити цикл, а також про те, що для змінних циклу не застосовується обсяг: вони зберігають останнє значення, на яке вони були встановлені.


10
@ MichałŠrajer, я думаю, ти мав на увазі двокрапку, а не коми;)
— Paweł Nadolski,


4
Зі старим Solaris, зі старою оболонкою Bourne (не POSIX) я маю писати "for last in" $ @ "; do true; done"
— mcoolive

8
@mcoolive @LaurenceGolsalves, окрім того, що є більш портативним, for last in "$@"; do :; doneтакож робить наміри набагато зрозумілішими.
— Адріан Гюнтер

1
@mcoolive: він працює навіть в оболонці bourne unix v7 з 1979 року. Ви не можете отримати більш портативний, якщо він працює як на v7 sh, так і на POSIX sh :)
— Dominik R

291

Це лише Bash:

echo "${@: -1}"

43
Для тих, хто (як мене) цікавить, навіщо потрібен простір, людина Баш має про це сказати:> Зауважте, що негативне зміщення повинно бути відокремлене від товстої кишки хоча б одним пробілом, щоб не плутати з: - розширенням.
— foo

1
Я використовував це, і він руйнується в базі MSYS2 лише у Windows. Химерність.
— Стівен Лу

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

Простір необхідний, щоб він не трактувався як значення за замовчуванням .

Зауважте, що це лише баш.


9
Найкраща відповідь, оскільки вона включає в себе наступний аргумент. Дякую!
— e40

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

Зауважте, що це лише баш.


Хоча приклад призначений для функції, сценарії також працюють аналогічно. Мені подобається ця відповідь, тому що вона чітка і стисла.
— Jonah Braun

1
І це не хакі. Він використовує явні функції мови, а не побічні ефекти або спеціальні qwerks. Це має бути прийнятою відповіддю.
— musicin3d

25

Виявив це, шукаючи відокремити останній аргумент від усіх попередніх аргументів. Хоча деякі відповіді отримують останній аргумент, вони не дуже допомагають, якщо вам знадобляться і всі інші аргументи. Це працює набагато краще:

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

Зауважте, що це лише баш.


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}отримати третій останній і так далі ...)
— олібре

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.
— олібре

12

Ось моє рішення:

  • досить портативний (всі POSIX sh, bash, ksh, zsh) повинен працювати
  • не зміщує оригінальні аргументи (зміщує копію).
  • не вживає зла eval
  • не повторюється через весь список
  • не використовує зовнішніх інструментів

Код:

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

1
Відмінна відповідь - коротка, портативна, безпечна. Дякую!
— Ján Lalinský

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

дякую @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

Або якщо ви хочете встановити lastvar:

$ 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.
— Призупинено до подальшого повідомлення.

@Dennis: Приємно! Я не знав про $((...))синтаксис.
— Лоранс Гонсалвс

Ви хочете використовувати це 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!
— користувач3295674

2

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

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

Зауважте, що це лише баш.


Як би ви перетворили голови в масив?
— Стефан

Я вже робив додавання дужок, наприклад, echo "${heads[-1]}"друкує останній елемент у heads. Або я щось пропускаю?
— EndlosSchleife

1

Рішення з використанням eval:

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

echo $last

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

Дивіться більш чіткий спосіб виконати те ж саме в іншій відповіді .
— Палець

@MestreLion котирування не потрібні на RHS від =.
— Том Хейл

1
@TomHale: вірно для конкретного випадку ${!#}, але не в цілому: цитати все ще потрібні, якщо вміст містить буквальний пробіл, наприклад last='two words'. Лише $()безпечний пробіл, незалежно від вмісту.
— MestreLion

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 "останнє значення"
— Томас

Дякую, що перевірили Томаса, я намагався виконати такий сценарій, як ти mentinoed # ./arguments.sh "останнє значення" Останній аргумент: значення працює зараз нормально. # ./arguments.sh "перевірка останнього значення з подвійним" Останній аргумент: подвійний
— 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

Для повернення останнього аргументу останньо використаної команди використовуйте спеціальний параметр:

$_

У цьому випадку він буде працювати, якщо він буде використаний у сценарії до виклику іншої команди.


Про це не
— задається

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.