Какой последний аргумент в предыдущей команде?

12

$_ называется последним аргументом предыдущей команды.

Так что мне интересно, почему это не так, EDITOR="emacs -nw"но EDITORв следующем примере?

Почему не "emacs -nw"часть последнего аргумента?

В более общем смысле, каковы определения аргумента и последнего аргумента?

Благодарю.

$ export EDITOR="emacs -nw"
$ echo $_
EDITOR
Тим
источник
3
Я думаю, по той же причине, что shellcheck говорит вам не экспортировать переменные в той же строке, которую вы им назначаете. Назначение происходит, а затем переменная экспортируется. EDITORаргумент для экспорта
jesse_b
FWIW, pdkshи dashбудет включать в себя значение, которое было назначено, но ksh93будет вести себя как bashи.
Кусалананда
zsh:, export FOO=bar; echo $_печатает export.
ilkkachu
@Jesse_b Все дело в последнем аргументе / операнде (включая присвоенное значение), но это может быть связано с тем фактом, что exportэто встроенная утилита.
Кусалананда
ksh: typeset -x FOO=barзатем echo $_печатает FOO, но в Bash declare -x FOO=bar; echo $_печатает FOO=bar.
ilkkachu

Ответы:

13

Bash обрабатывает присвоения переменных, когда они допускаются в качестве аргументов (с alias, declare, export, local, readonly, и typeset), прежде чем что - либо (или , скорее, он идентифицирует их , прежде чем что - либо другое - расширение относится к значениям , назначенные переменным). Когда дело доходит до раскрытия слова, оставшаяся команда export EDITOR, поэтому _, устанавливается на EDITOR.

Вообще говоря, аргументы - это «слова», оставшиеся после раскрытия (которое не включает присваивание переменных и перенаправления).

Подробности смотрите в разделе Простое расширение команд в руководстве по Bash.

Стивен Китт
источник
И я понимаю, declareчто поведение не соответствует тому, что я описываю ...
Стивен Китт
Ну, это не очень последовательно в этом. declare a=b; echo $_отпечатки a=b; export c=d; echo $_печатает просто c. aliasкажется, печатает только имя, localс другой стороны, печатает весь аргумент. А readonlyтакже печатает только имя, которое я нахожу немного удивительным, так как я бы подумал, readonlyи localбудет похоже на declare.
ilkkachu
1
@ilkkachu хе, я тоже это понял (см. выше). exportи readonlyобъявляются вместе setattr.def, declare, localи typesetобъявлены в declare.def, aliasстоит особняком в alias.def.
Стивен Китт
Благодарю. Когда назначения переменных используются в качестве аргументов для некоторых команд, (1) «(или, скорее, они идентифицируют их раньше всего - расширение применяется к значениям, назначенным переменным)», вы имеете в виду, что расширение происходит со значениями перед выполнением назначения переменных? (2) «Когда дело доходит до раскрытия слова, оставшаяся команда - это EDITOR экспорта», вы имеете в виду выполнение назначения переменной до раскрытия? Две цитаты, кажется, противоречат друг другу.
Тим
Благодарю. Я немного запутался. Когда назначения переменных используются в качестве аргументов для alias, declare, export, local, readonlyи typeset. Что происходит первым и следующим? «Когда дело доходит до раскрытия слова, остальной командой является export EDITOR», подразумеваете ли вы, что присвоение переменной EDITOR="emacs -nw"происходит до раскрытия? Если нет, то почему оставшаяся команда не содержит присваивание в качестве аргумента? Если да, не должно ли произойти раскрытие значений, назначенных переменным, перед выполнением присваивания переменной?
Тим
4

TL; DR: В случае export FOO=barbash вызывает создание своего временного окружения, устанавливает его FOO=barв этом окружении, а затем выдает окончательную команду export FOO. В этот момент FOOпринимается за последний аргумент.


Ах, много-злоупотребляли $_:

($ _, подчеркивание.) При запуске оболочки задайте абсолютный путь, используемый для вызова оболочки или сценария оболочки, выполняемого в том виде, как он был передан в среде или списке аргументов. Затем расширяется до последнего аргумента предыдущей команды после расширения. Также задайте полный путь, используемый для вызова каждой команды, выполненной и помещенной в среду, экспортированную в эту команду. При проверке почты этот параметр содержит имя почтового файла.

Давайте посмотрим на несколько вариантов:

$ man; echo $_
What manual page do you want?
man
$ man foo; echo $_
No manual entry for foo
foo
$ echo; echo $_

echo
$ echo bar foo; echo $_
bar foo
foo
$ foo=x eval 'echo $foo'; echo $_
x
echo $foo
$ bar() { man $1; }; echo $_
foo
$ for (( i=0; $i<0; i=i+1 )); do echo $i; done; echo $_
foo
$ bar; echo $_
What manual page do you want?
man
$ bar foo; echo $_
No manual entry for foo
foo
$ MANPATH=/tmp; echo $_

$ export MANPATH=/tmp; echo $_
MANPATH

Итак, мы видим три модели здесь:

  • Команды, вызываемые из файловой системы, функций и встроенных модулей, ведут себя, как правило, ожидаемым: $_устанавливается само имя команды, если аргументов нет, в противном случае последний из представленных аргументов.
  • После определений функций, циклов и других логических конструкций: $_не изменяется.
  • Все остальное: $_настроено на что-то не совсем ожидаемое; странно.

Я инструктировал код, чтобы дать некоторое представление о странности.

$ ./bash --noprofile --norc -c 'man foo'
lastword=[man]
lastarg=[foo]
$ ./bash --noprofile --norc -c 'export FOO=bar'
lastword=[export]
lastarg=[FOO=bar]
bind_variable, name=[FOO], value=[bar]
before bind_lastarg, lastarg=[FOO]
bind_lastarg, arg=[FOO]
bind_variable, name=[_], value=[FOO]
$ ./bash --noprofile --norc -c 'declare FOO=bar'
lastword=[declare]
lastarg=[FOO=bar]
bind_variable, name=[FOO], value=[(null)]
before bind_lastarg, lastarg=[FOO=bar]
bind_lastarg, arg=[FOO=bar]
bind_variable, name=[_], value=[FOO=bar]

Вы можете видеть, что парсер видит ожидаемый последний аргумент ( lastarg=) во всех случаях, но то, что произойдет после этого, зависит от того, что думает Bash. Смотрите execute_cmd.c, execute_simple_command () .

В случае export FOO=barbash выполняет присваивание, а затем экспортирует переменную. Это похоже на утверждение документации о том, что последний аргумент вычисляется после расширения.

епископ
источник
1
Как оболочка узнает, что вы проверяете почту?
rackandboneman
@rackandboneman, не подтверждая, я подозреваю, что внутренние проверки сделаны на основеMAILCHECK
Джефф Шаллер
2

Чтобы ответить на заглавный вопрос, попробуйте !$:

$ export EDITOR="emacs -nw"
$ echo !$
EDITOR=emacs -nw

Это расширение истории. Из справочной страницы bash:

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

...

Указатели событий

...

! Запустите подстановку истории, за исключением случаев, когда за ней следует пробел, символ новой строки, возврат каретки, = или ((когда опция оболочки extglob включена с помощью встроенной функции shopt).

...

!! Обратитесь к предыдущей команде. Это синоним `! -1 '.

...

Обозначения слов

...

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

...

Если указатель слова предоставляется без спецификации события, в качестве события используется предыдущая команда.

Йол
источник
Вы слишком буквально понимаете название вопроса. (Хорошо, это плохой заголовок.) Мы все видим, что команда « export EDITOR="emacs -nw"» состоит из двух слов: первое - « export», а второе - « EDITOR="emacs -nw"». На самом деле возникает вопрос: «Что означают страница руководства по bash и руководство по Bash, когда они говорят, что !_« расширяется до последнего аргумента предыдущей команды », учитывая, что в этом случае bash устанавливает $_« EDITOR»?» Копирование и вставка раздела справочной страницы bash по расширению истории не особенно полезны.
G-Man говорит: «Восстановите Монику»