Почему восклицательный знак `!` Иногда расстраивает Баш?

14

Я понимаю, что это !имеет особое значение для командной строки в контексте истории командной строки, но, кроме этого, в рабочем скрипте восклицательный знак может иногда вызывать ошибку синтаксического анализа.
Я думаю , что это что - то делать с event, но я понятия не имею , что такое событие или что он делает. Несмотря на это, одна и та же команда может вести себя по-разному в разных ситуациях.
Последний пример ниже вызывает ошибку; но почему, когда тот же код работал вне подстановки команд? .. используя GNU bash 4.1.5

# This works, with or without a space between ! and p
  { echo -e "foo\nbar" | sed -nre '/foo/! p'
    echo -e "foo\nbar" | sed -nre '/foo/!p'; }
# bar
# bar

# This works, works when there is a space between ! and p
  var="$(echo -e "foo\nbar" | sed -nre '/foo/! p')"; echo "$var"
# bar

# This causes an ERROR, with NO space between ! and p
  var="$(echo -e "foo\nbar" | sed -nre '/foo/!p')"; echo "$var"
# bash: !p': event not found
Peter.O
источник
1
См. Unix.stackexchange.com/questions/3747/…
Уоррен Янг
@ Уоррен .. Спасибо. Я видел этот QA, но на самом деле он говорит только о том, как избежать обратной косой черты ... Моя проблема больше связана с тем, почему, казалось бы, уже экранированный код работает в одной ситуации, а не в другой ...
Peter.O
@fred: "похоже уже сбежал"? Я не вижу никаких побегов, а вы используете двойные кавычки. Смотрите мой (исправленный) ответ. Какую часть, по вашему мнению, удалось избежать?
Калеб
@Caleb. Да, я использовал неправильный термин .. protectedбыло бы более подходящим. (защищено «одинарными кавычками»)
Peter.O
Если вас интересуют только простые задания, то вы можете использовать var=$(…)(без двойных кавычек), и это будет работать так, как вы думаете. Это все еще «безопасно», потому что часть значения простого присваивания не подвержена разбиению или смещению слов (хотя это может быть не так для присваиваний, выполняемых через встроенные функции (например export, localи т. Д.) Во всех оболочках). К сожалению, это не выходит за рамки простых присваиваний, поскольку двойные кавычки - это способ защиты от разбиения и разбивки слов, в то же время получая другие типы раскрытия в других контекстах.
Крис Джонсен

Ответы:

12

!Персонаж вызывает замену истории в Bash. Когда за ним следует строка (как в вашем неудачном примере), он пытается развернуться до последнего события истории, которое началось с этой строки. Точно так же, как $varрасширяется до значения этой строки, !echoбудет расширяться до последней команды эха в вашей истории.

В таких расширениях пробел является символом разрушения. Сначала обратите внимание, как это будет работать с переменными:

# var="like"
# echo "$var"
like
# echo "$"
$
# echo "Do you $var frogs?"
Do you like frogs?       <- as expected, variable name broken at space
# echo "Do you $varfrogs?"
Do you?                  <- $varfrogs not defined, replaced with blank
# echo "Do you $ var frogs?"
Do you $ var frogs?      <- $ not a valid variable name, ignored

То же самое произойдет для расширения истории. Символ взрыва ( !) начинается с последовательности замены истории, но только если за ней следует строка. После пробела сделайте буквальный удар вместо части последовательности замены.

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

Калеб
источник
Спасибо, Калеб ... Еще одна из моих предварительных концепций была отклонена ... Я думал, что разбор bash был сделан из самой внутренней скобки или фигурной скобки, а затем сработал наружу ... Кажется, что bash анализирует иначе, чем я предполагал.
Peter.O
1
Цитирование достаточно запутанно в bash без изменения во вложенных строках. Как таковые, замены происходят очень рано в процессе. Рассмотрим этот пример:var=word; echo "test '$var'"; echo 'test "$var"'
Калеб
.. Да не понял. Мне было известно о вложении кавычек в кавычки ... Мое недопонимание заключалось в том, что я думал, что код в скобках подстановки команд будет разбираться отдельно, чтобы окружить эти скобки; но, видимо, нет .. спасибо.
Peter.O
6

Как уже сказал Калеб , !используется для вызова подмены истории bash.

Если, как и я, вы чувствуете, что вам не нужна такая функция, вы можете отключить ее, вставив следующую строку ~/.bashrc:

set +H

Мне это не нужно, потому что история может быть восстановлена ​​с помощью стрелки вверх и Ctrl- rинкрементального обратного поиска. См. Страницу руководства bash, раздел « Команды для манипулирования историей» для подробного списка ярлыков.

enzotib
источник
2
Как ты живешь без !!?
Калеб
Спасибо. Я думаю, что это может быть проблемой, с точки зрения переносимости, но использование set +Hв сценарии работает так же хорошо :) +1
Peter.O
2
@fred: странно, обычно расширение истории включено только для интерактивных оболочек.
энзотиб
@enzo .. Еще раз спасибо .. Я проверил это из командной строки .. Ах! Если бы обучение не было таким веселым, это было бы утомительно ... я упоминал кофе? это тоже помогает :)
Peter.O
Да, это гоча. Я скопировал вставленный код из сценариев, которые потерпели неудачу в командной строке именно по этой причине. Расширение истории не было проблемой в сценарии, но это на интерактивной оболочке.
Калеб
2

Ваш первый пример:

{ echo -e "foo\nbar" | sed -nre '/foo/! p'
    echo -e "foo\nbar" | sed -nre '/foo/!p'; }

может быть уменьшено до

echo '! p' 
echo '!p'

В одинарных кавычках все символы сохраняют свои буквальные значения. Таким образом !потерял свое особое значение и расширение истории не было сформировано.

ваш второй и третий примеры:

var="$(echo -e "foo\nbar" | sed -nre '/foo/! p')"; echo "$var"

var="$(echo -e "foo\nbar" | sed -nre '/foo/!p')"; echo "$var"

может быть уменьшено до

echo "'! p'"

echo "'!p'"

'! p'и '!p'по существу являются частями строк в двойных кавычках.

В двойных кавычках все символы сохраняют свои буквальные значения , за исключением $ , `, \и !.

Это подразумевает одинарные кавычки '! p'и '!p'утратило их особое значение (т.е. неспособность убежать !), но !все еще сохраняет свое особое значение, таким образом, выполняется расширение истории.

Однако, когда !за ним следует символ пробела, расширение истории не выполняется.

Цитирование из man bash:

квотирование

[...]

Заключение символов в одинарные кавычки сохраняет буквальное значение каждого символа в кавычках. [...]

Заключение символов в двойные кавычки сохраняет буквальное значение всех символов в кавычках, за исключением $, `, \ и, когда расширение истории включено,!. [...] Если включено, расширение истории будет выполняться, если только! в двойных кавычках экранируется обратная косая черта. Обратная косая черта предшествует! не удаляется.

ИСТОРИЯ РАСШИРЕНИЯ

[...]

Расширения истории представлены появлением персонажа расширения истории, которое есть! по умолчанию. Только обратная косая черта (\) и одинарные кавычки могут указывать символ расширения истории.

Несколько символов запрещают раскрытие истории, если обнаружены сразу после символа расширения истории, даже если оно не заключено в кавычки: пробел, табуляция, перевод строки, возврат каретки и =. Если опция оболочки extglob включена, (также будет препятствовать расширению.

cychoi
источник