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


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.