Вопрос или проблема
Я пытался создать скрипт, используя echo
для записи содержимого в файл, вместо того чтобы открывать его в редакторе
echo -e "#!/bin/bash \n /usr/bin/command args" > .scripts/command
Вывод:
bash: !/bin/bash: событие не найдено
Я изолировал это странное поведение к восклицательному знаку.
$ echo !
!
$ echo "!"
bash: !: событие не найдено
$ echo \#!
#!
$ echo \#!/bin/bash
bash: !/bin/bash: событие не найдено
- Почему восклицательный знак вызывает это?
- Что такое эти “события”, о которых говорит bash?
- Как мне обойти эту проблему и напечатать “#!/bin/bash” на экране или в файл?
Попробуйте использовать одинарные кавычки.
echo -e '#!/bin/bash \n /usr/bin/command args' > .scripts/command
echo '#!'
echo '#!/bin/bash'
Проблема возникает из-за того, что bash ищет в своей истории !/bin/bash. Использование одинарных кавычек устраняет это поведение.
Как сказал Richm, bash пытается выполнить соответствие в истории. Другой способ избежать этого — просто экранировать восклицательный знак с помощью \
:
$ echo \#\!/bin/bash
#!/bin/bash
Однако будьте осторожны: внутри двойных кавычек \
не удаляется:
$ echo "\!"
\!
!
начинает подстановку истории («событие» — это строка в истории команд); например, !ls
расширяется до последней строки команды, содержащей ls
, а !?foo
расширяется до последней строки команды, содержащей foo
. Вы также можете извлекать конкретные слова (например, !!:1
относится к первому слову предыдущей команды) и многое другое; смотрите справку для подробностей.
Эта функция была изобретена для быстрого вызова предыдущих команд в те времена, когда редактирование командной строки было примитивным. С современными оболочками (по крайней мере bash и zsh) и функцией копирования и вставки, расширение истории не так полезно, как это было раньше — оно все еще полезно, но вы можете обойтись без него.
Вы можете изменить, какой символ вызывает подстановку истории, установив переменную histchars
; если вы редко используете подстановку истории, вы можете установить, например, histchars="¡^"
, чтобы ¡
вызывало расширение истории вместо !
. Вы даже можете совсем отключить эту функцию с помощью set +o histexpand
.
Чтобы иметь возможность отключить расширение истории в конкретной строке команды, вы можете использовать space
в качестве 3-го символа $histchars
:
histchars="!^ "
Тогда, если вы введете команду с ведущим пробелом, расширение истории не будет выполняться.
bash-4.3$ echo "#!/bin/bash"
bash: !/bin/bash: событие не найдено
bash-4.3$ echo "#!/bin/bash"
#!/bin/bash
Однако обратите внимание, что ведущие пробелы также используются, когда $HISTCONTROL
содержит ignorespace
в качестве способа указать bash
, чтобы не записывать строку команды в истории.
Если вы хотите, чтобы обе функции работали независимо, вам нужно выбрать другой символ в качестве 3-го символа $histchars
. Вам нужен такой, который не влияет на интерпретацию вашей команды. Несколько вариантов:
- использование обратного слэша (
\
):\echo foo
работает, но это имеет побочный эффект отключения алиасов или ключевых слов. - TAB: чтобы вставить его в первую позицию, вам нужно нажать Ctrl+VTab.
-
если вас не смущает нажатие двух клавиш, вы можете выбрать любой символ, который обычно не появляется в первой позиции (
%
,@
,?
, выберите свой вариант) и создать для него пустой алиас:histchars="!^%" alias %=
Затем введите этот символ, пробел и вашу команду:
bash-4.3$ % echo !! !!
(однако вы не сможете не записать команду, где подстановка истории была отключена. Также обратите внимание, что 3-й символ по умолчанию для $histchars
— это #
, что означает, что расширение истории не выполняется в комментариях. Если вы измените это и введете комментарии в командной строке, вы должны учитывать, что последовательности ! могут быть расширены там).
Предложенные решения не работают, например, в следующем примере:
$ bash -c "echo 'hello World!'"
-bash: !'": событие не найдено
$
В этом случае восклицательный знак можно напечатать, используя его восьмеричный ASCII код:
$ bash -c "echo -e 'hello World\0041'"
hello World!
$
Простое решение, которое еще не было упомянуто, — использовать переменную:
$ var="!"
$ echo -e "#${var}/bin/bash \n /usr/bin/command args" > .scripts/command
Печать bang!
не является проблемой, когда строка не заключена в кавычки, или находится в одинарных кавычках, или внутри C-строки
$ echo hi\!pal 'hi!pal' $'hi!pal'
hi!pal 'hi!pal' $'hi!pal'
Также не будет проблемой, когда расширение истории отключено (либо shopt -ou histexpand
, либо set +o histexpand
, либо просто set +H
. Также возможно изменить символы расширения истории, чтобы облегчить печать bang!
.
Но самое простое портативное решение: используйте переменную
$ var="!"
$ echo "hi${var}pal"
hi!pal
Для меня простым решением было временно переключиться на sh
вместо bash
.
Ответ или решение
Эхо в командной строке — это мощный инструмент для вывода данных, однако использование определённых символов может привести к неожиданным результатам. Одним из таких символов является "восклицательный знак" (bang, !
), который в оболочке Bash используется для расширения истории команд. В данной статье мы подробно рассмотрим, почему возникает данная проблема, что такое "события" (events) в Bash и как можно обойти эту проблему, чтобы вывести строку #!/bin/bash
в файл или на экран.
Почему восклицательный знак вызывает такую ошибку?
При вводе команды вроде echo "#!/bin/bash"
или echo "#!/bin/bash" > filename
, Bash интерпретирует восклицательный знак как начало команды для замены истории. Например, если вы ввели echo !
, Bash попытается подсказать последнюю команду или строки, содержащие символ !
. Если такой команды нет в истории, вы получите ошибку: bash: !: event not found
.
Что такое события (events) в Bash?
События в Bash относятся к строкам в истории команд. Каждый раз, когда вы вводите команду, она сохраняется в истории, и вы можете вернуться к ней позже с помощью специальных символов, таких как !
(восклицательный знак). Например, команда !!
выполнит последнюю команду, а !n
выполнит n-ю команду из истории. Однако, если !
не находит подходящего события, будет отображено сообщение об ошибке.
Как избежать проблемы?
Существует несколько способов корректно вывести строку с восклицательным знаком:
-
Использование одинарных кавычек:
Один из простых способов избежать интерпретации символа!
— обернуть строку в одинарные кавычки. Используя одинарные кавычки, вы предотвратите любое расширение и получите желаемый результат.echo '#!/bin/bash' > .scripts/command
-
Экранирование:
Вы можете экранировать восклицательный знак с помощью обратной косой черты.echo \#!/bin/bash
Однако будьте осторожны: внутри двойных кавычек экранирование работает иначе.
-
Изменение переменной
histchars
:
Если вы часто сталкиваетесь с подобными проблемами, вы можете изменить символы, которые триггерят замены истории. Например:histchars="¡^"
После этого символ
!
не будет использоваться для истории, и вы сможете вводить строки с восклицательным знаком без проблем. -
Отключение расширения истории:
Вы можете временно отключить расширение истории команд с помощью команды:set +o histexpand
После этого все команды будут обрабатываться без применения истории.
-
Использование переменных:
Один из простых и универсальных способов — это использование переменной:var="!" echo "#${var}/bin/bash" > .scripts/command
Эти методы позволяют избежать неприятных ошибок при работе с Bash и успешно выводить строки, содержащие символ восклицательного знака.
Заключение
В заключение, восклицательный знак в Bash может вызывать неожиданные проблемы при выводе строк. Понимание работы с символами истории и применение правильных методов поможет вам избежать ошибок и эффективно использовать командную строку. Следуя предложенным советам, вы сможете легко и быстро выводить желаемые строки в файлы или на экран без лишних сложностей.