Как вызвать восклицание!

Вопрос или проблема

Я пытался создать скрипт, используя 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-ю команду из истории. Однако, если ! не находит подходящего события, будет отображено сообщение об ошибке.

Как избежать проблемы?

Существует несколько способов корректно вывести строку с восклицательным знаком:

  1. Использование одинарных кавычек:
    Один из простых способов избежать интерпретации символа ! — обернуть строку в одинарные кавычки. Используя одинарные кавычки, вы предотвратите любое расширение и получите желаемый результат.

    echo '#!/bin/bash' > .scripts/command
  2. Экранирование:
    Вы можете экранировать восклицательный знак с помощью обратной косой черты.

    echo \#!/bin/bash

    Однако будьте осторожны: внутри двойных кавычек экранирование работает иначе.

  3. Изменение переменной histchars:
    Если вы часто сталкиваетесь с подобными проблемами, вы можете изменить символы, которые триггерят замены истории. Например:

    histchars="¡^"

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

  4. Отключение расширения истории:
    Вы можете временно отключить расширение истории команд с помощью команды:

    set +o histexpand

    После этого все команды будут обрабатываться без применения истории.

  5. Использование переменных:
    Один из простых и универсальных способов — это использование переменной:

    var="!"
    echo "#${var}/bin/bash" > .scripts/command

Эти методы позволяют избежать неприятных ошибок при работе с Bash и успешно выводить строки, содержащие символ восклицательного знака.

Заключение

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

Оцените материал
Добавить комментарий

Капча загружается...