Большинство пользователей Linux сталкиваются с символом ! лишь в двух случаях: когда узнают про приём sudo !! или когда пароль с восклицательным знаком внутри двойных кавычек приводит к ошибке event not found. Всё остальное, что умеет !, часто остаётся незамеченным годами.
Причина в том, что у ! в Bash есть две совершенно разные функции. В интерактивном терминале он используется для подстановки из истории, которая позволяет быстро вызывать, повторно использовать и изменять уже выполненные команды.
В проверках, шаблонах и подстановке параметров ! применяется для логического отрицания, и это работает как в интерактивных оболочках, так и в скриптах.
В этом руководстве рассмотрены 10 практических способов применения символа !: от вызова и изменения предыдущих команд до логического отрицания в скриптах и шаблонов исключения.
Все примеры проверены в Bash 5.2 и работают на современных дистрибутивах Linux. Некоторые возможности подстановки истории доступны и в оболочках zsh и ksh (KornShell), но оболочки dash и POSIX sh не поддерживают её вообще.
Эта разница становится особенно важной, когда вы начинаете писать скрипты.
1. Запуск команды из истории по номеру
Каждая команда, выполненная в Bash, получает в истории команд уникальный номер события. Если номер известен, команду можно мгновенно выполнить повторно, не набирая её заново.
Сначала выведите историю команд:
history
Пример вывода:
1549 pwd
1550 ls -l
1551 top
1552 df -h
Вывод показывает нумерованный список ранее выполненных команд. Чтобы повторно выполнить одну из них, введите ! и сразу после него номер события:
!1551
Bash подставит вместо !1551 команду, сохранённую под номером 1551, и сразу выполнит её. В этом примере это:
top
Номера событий на вашей системе будут отличаться, поэтому перед повторным запуском проверьте нужный номер с помощью команды history.
Важно: между ! и номером события не должно быть пробела. !1551 запускает подстановку истории, а ! 1551 интерпретируется иначе, и Bash попытается выполнить команду с именем 1551, что приведёт к ошибке command not found.
2. Запуск ранее выполненных команд по относительной позиции
Не всегда нужно помнить номер события. Bash также позволяет ссылаться на команды по их позиции относительно текущей, что удобно, когда нужно повторить что-то из последних команд.
Последняя выполненная команда обозначается как !-1, предыдущая перед ней как !-2, затем !-3 и так далее.
Например:
history
!-6
!-8
!-10
В этом примере:
!-6запускает команду, выполненную шесть команд назад.!-8запускает команду, выполненную восемь команд назад.!-10запускает команду, выполненную десять команд назад.
В отличие от абсолютных номеров событий, например !1551, относительные ссылки меняются после каждой новой команды, потому что они всегда отсчитываются от текущей позиции в списке истории.
Если непонятно, на какую команду указывает относительная ссылка, можно сначала посмотреть её с помощью модификатора :p, о котором рассказано ниже.
Относительные ссылки удобны для быстрого повторения одной из недавних команд. Однако если ту же команду нужно запустить позже в той же сессии, надёжнее использовать её номер события, например !1551, потому что в текущей сессии оболочки он не меняется.
3. Повторное использование аргументов предыдущей команды
Один из самых заметных приёмов экономии времени в Bash это повторное использование аргументов только что выполненной команды. Вместо повторного набора длинных путей к файлам или каталогам их можно подставить с помощью специальных указателей слов в подстановке истории.
Например, вы просматриваете каталог:
ls /home/$USER/Binary/firefox
Через мгновение понимаете, что нужен был длинный формат списка. Вместо повторного ввода всего пути используйте !$, чтобы подставить последний аргумент предыдущей команды:
ls -l !$
Перед выполнением Bash подставит вместо !$ следующее:
ls -l /home/$USER/Binary/firefox
Распространённые указатели слов. Перечисленные ниже сокращения подстановки истории ссылаются на предыдущую команду:
!$подставляет последний аргумент.!^подставляет первый аргумент.!*подставляет все аргументы, кроме имени команды.
Например, если создать несколько каталогов:
mkdir -p /srv/app/logs /srv/app/cache /srv/app/tmp
Сразу после этого можно использовать тот же список каталогов с другой командой:
chown -R www-data:www-data !*
Так не придётся заново набирать длинные списки аргументов и меньше шансов ошибиться.
Сочетание клавиш Alt+.. Если не хочется использовать подстановку истории, в Bash есть удобное сочетание клавиш, которое делает похожую работу.
Нажмите Alt+точка (или Esc, затем .), чтобы вставить последний аргумент предыдущей команды прямо в позицию курсора.
При повторных нажатиях сочетание перебирает последние аргументы более ранних команд, позволяя быстро подставить путь без повторного ввода.
4. Повторное использование конкретного аргумента предыдущей команды
Иногда нужен только конкретный аргумент из ранее выполненной команды. С помощью указателей слов Bash позволяет получить аргументы по их позиции.
Например, вы копируете файл:
cp /home/avi/Desktop/1.txt /home/avi/Downloads
Теперь нужно повторно использовать исходный путь или путь назначения, не набирая его заново:
echo "1st argument was: !^"
echo "2nd argument was: !cp:2"
Перед выполнением Bash подставит вместо них:
echo "1st argument was: /home/avi/Desktop/1.txt"
echo "2nd argument was: /home/avi/Downloads"
Как работает позиция аргументов. При обращении к аргументам по позиции Bash считает их так:
cp /home/avi/Desktop/1.txt /home/avi/Downloads
│ │ │
0 1 2
- 0 это имя команды (cp).
- 1 это первый аргумент.
- 2 это второй аргумент.
Чтобы обратиться к аргументу конкретной команды, используйте:
!command:position
Например, если ранее была выполнена команда:
xyz one two three four five
Тогда:
!xyz:1подставляется как one!xyz:4подставляется как four
Другие полезные способы выбора аргументов. Bash также поддерживает выбор диапазонов аргументов:
!:0повторно использует имя предыдущей команды.!:2повторно использует второй аргумент предыдущей команды.!:2-4повторно использует аргументы со 2 по 4.!:3*повторно использует аргументы со 2 по 4.!*все аргументы, кроме имени команды.
Такие ссылки особенно полезны при работе с длинными путями или командами с большим числом аргументов: можно повторно использовать только нужные части, а не набирать всё заново.
5. Запуск последней команды, которая начинается с ключевого слова
Одна из самых полезных возможностей подстановки истории это вызов команд по тексту, с которого они начинаются. И одновременно она чаще всего вызывает недопонимание.
Правильный синтаксис: !, затем сразу искомый текст без пробела. Чтобы посмотреть, как это работает, выполните несколько команд, которые начинаются с ls:
ls /home > /dev/null # Command 1
ls -l /home/avi/Desktop > /dev/null # Command 2
ls -la /home/avi/Downloads > /dev/null # Command 3
ls -lA /usr/bin > /dev/null # Command 4
Теперь повторно выполните последнюю команду, которая начинается с ls:
!ls
Bash подставит её так:
ls -lA /usr/bin > /dev/null
Хотя с ls начинаются все четыре команды, Bash выполнит только самую последнюю подходящую. Он не показывает список совпадений и не даёт выбрать из них.
Распространённая ошибка. Многие ожидают, что следующая команда вызовет последнюю команду, начинающуюся с ls -l:
!ls -l
На деле Bash сначала подставит вместо !ls последнюю команду, начинающуюся с ls, а затем добавит оставшийся текст:
ls -lA /usr/bin > /dev/null -l
Иными словами, аргумент -l считается новым аргументом, а не частью поиска по истории.
Поиск в любом месте команды. Если не помните, как начиналась команда, Bash также позволяет искать текст в любом месте командной строки с помощью !?string?.
!?Desktop?
Bash найдёт последнюю команду, в которой слово Desktop встречается в любом месте командной строки, и подставит:
ls -l /home/avi/Desktop > /dev/null
Закрывающий ? необязателен, если искомая строка находится в конце строки.
- Используйте
!string, чтобы найти последнюю команду, которая начинается с string. - Используйте
!?string?, чтобы найти последнюю команду, в которой строка string встречается где угодно.
Совет. Если несколько записей истории содержат одну и ту же строку поиска, Bash всегда подставляет самую последнюю подходящую команду. Чтобы перед выполнением увидеть, что именно будет запущено, добавьте модификатор :p, например !?Desktop?:p или !ls:p. Он выводит подставленную команду, но не выполняет её. Подробнее о модификаторе :p пойдёт речь ниже.
6. Повтор предыдущей команды с помощью !!
Подстановка !! это один из самых популярных приёмов в Bash. Она разворачивается в полную предыдущую команду, поэтому хорошо подходит для повторного запуска длинных команд без их набора.
Чаще всего !! используют после ошибки с правами доступа. Например, вы забыли выполнить команду через sudo:
systemctl restart nginx
Если появилась ошибка доступа:
Failed to restart nginx.service: Access denied
Выполните:
sudo !!
Bash подставит:
sudo systemctl restart nginx
Так можно перезапустить команду с повышенными привилегиями без повторного набора.
Поскольку !! разворачивается в целую предыдущую команду, к ней можно добавлять дополнительные аргументы и перенаправления.
Например, вы выводите IPv4-адрес системы:
ip -4 -o addr show | awk '{print $4}' | cut -d/ -f1
Если позже понадобится сохранить вывод в файл, выполните:
!! > ip.txt
Bash подставит:
ip -4 -o addr show | awk '{print $4}' | cut -d/ -f1 > ip.txt
На системах без sudo можно использовать su. При этом обязательно используйте двойные кавычки, потому что внутри одинарных кавычек подстановка истории не работает.
su -c "!!" root
Двойные кавычки важны, потому что Bash выполняет подстановку истории внутри строк в двойных кавычках до передачи команды в su. В одинарных кавычках !! останется обычным текстом и не подставится.
Распространённое заблуждение: любую упавшую команду можно исправить, добавив sudo. Это верно, только если сбой вызван нехваткой прав. Например, если выполнить команду ifconfig и получить ошибку command not found, проблема обычно в том, что пакет net-tools не установлен, а не в правах администратора.
Установите его через пакетный менеджер вашего дистрибутива:
Ubuntu и Debian
sudo apt install net-tools
RHEL, Rocky Linux и Fedora
sudo dnf install net-tools
Используйте sudo !!, когда команда не выполняется из-за прав, а не когда сама команда отсутствует.
7. Просмотр подстановки перед выполнением с помощью :p
Подстановка истории выполняется до запуска команды. После нажатия Enter подставленная команда сразу выполняется, что неудобно, если вы подставляете потенциально опасные команды.
Чтобы увидеть, что Bash выполнит, без фактического запуска, добавьте к подстановке модификатор :p (print).
Например:
!rm:p
Если последняя команда, начинающаяся с rm, была:
rm -rf /var/tmp/build-cache
Bash выведет:
rm -rf /var/tmp/build-cache
Обратите внимание: ничего не выполняется. Модификатор :p только выводит подставленную команду, чтобы вы могли убедиться, что Bash нашёл нужную запись в истории, до её запуска. Это работает с любой подстановкой истории, включая:
!!:p !1551:p !?Desktop?:p
Использовать :p полезно, когда подставляете команды, которые удаляют файлы, перезаписывают данные, меняют конфигурацию системы или перезапускают сервисы.
Если хотите всегда предварительно просматривать подстановку истории, включите параметр Bash histverify:
shopt -s histverify
Чтобы закрепить такое поведение, добавьте ту же команду в файл ~/.bashrc:
echo 'shopt -s histverify' >> ~/.bashrc
source ~/.bashrc
Когда histverify включён, Bash подставляет историю и возвращает получившуюся команду в командную строку, а не выполняет её сразу. Можно просмотреть, отредактировать или отменить команду до нажатия Enter. Так подстановка истории становится заметно безопаснее для повседневной работы.
8. Исправление опечатки в предыдущей команде с помощью быстрой замены
Если в предыдущей команде допущена небольшая опечатка, не нужно набирать всю строку заново. Функция быстрой замены Bash позволяет заменить один фрагмент текста и сразу повторно выполнить исправленную команду.
Синтаксис:
^old^new^
Например, вы ошиблись в названии сервиса:
systemctl status ngnix
Вместо повторного набора всей команды выполните:
^ngnix^nginx^
Bash подставит и выполнит:
systemctl status nginx
Быстрая замена работает только с непосредственно предыдущей командой, поэтому она хорошо подходит для исправления мелких опечаток. По умолчанию заменяется только первое вхождение совпавшего текста.
Например:
vim /etc/nginx/nginx.conf
Теперь замените nginx на apache:
^nginx^apache^
Bash выполнит:
vim /etc/apache/nginx.conf
Обратите внимание: заменилась только первая строка nginx, второе вхождение осталось без изменений. Чтобы заменить все вхождения строки, используйте модификатор полной замены с !!:
!!:gs/nginx/apache/
Bash подставит:
vim /etc/apache/apache.conf
Здесь:
!!ссылается на предыдущую команду.- Обратите внимание: заменилась только первая строка nginx, второе вхождение осталось без изменений.
gозначает global, то есть замену всех совпадений.sвыполняет замену.
Как и другие подстановки истории, замены поддерживают модификатор :p. Если изменяете длинные команды или важные пути, лучше сначала посмотреть результат:
!!:gs/nginx/apache/:p
Bash выведет подставленную команду, но не выполнит её. Так можно проверить замену до нажатия Enter.
9. Обработка путей к файлам с помощью модификаторов слов
Подстановка истории не только возвращает предыдущие аргументы, но и умеет изменять их с помощью модификаторов слов. Такие модификаторы особенно удобны при работе с длинными путями: не придётся вызывать команды вроде dirname и basename.
Модификаторы слов добавляются после двоеточия (:) и работают с любым указателем слов в истории.
Например, вы редактируете конфигурационный файл:
vim /etc/nginx/nginx.conf
Из последнего аргумента (!$) можно извлечь разные части с помощью таких модификаторов:
!$:hвозвращает начало пути, всё до последнего слэша, то есть/etc/nginx!$:tвозвращает хвост, только имя файла, то естьnginx.conf!$:rубирает расширение файла:/etc/nginx/nginx!$:eвозвращает только расширение файла:conf
Эти модификаторы работают полностью внутри подстановки истории Bash, поэтому внешние команды не нужны.
Практический пример. Одно из самых полезных сочетаний: отредактировать файл и сразу перейти в его родительский каталог.
vim /srv/www/example.com/config/settings.yml
cd !$:h
Перед выполнением второй команды Bash подставит:
cd /srv/www/example.com/config
Так не придётся копировать и править длинные пути вручную. Модификаторы слов можно сочетать и с другими подстановками истории.
Например, !!:$:t вернёт имя файла из последнего аргумента предыдущей команды, а !1551:$:h извлечёт родительский каталог из последнего аргумента команды под номером 1551.
10. Удаление всего, кроме выбранных файлов
Символ ! в !(pattern) не относится к подстановке истории. Это часть расширенного globbing, который позволяет исключать файлы, соответствующие заданному шаблону.
По умолчанию extended globbing в Bash выключен, поэтому перед использованием таких шаблонов его нужно включить.
Сначала проверьте текущее состояние:
shopt extglob
Если в выводе:
extglob off
включите его командой:
shopt -s extglob
После включения шаблон:
!(pattern)
соответствует всем именам файлов, кроме тех, которые подходят под pattern.
Например, чтобы удалить всё в текущем каталоге, кроме important_file.txt:
rm !(important_file.txt)
Чтобы сохранить все PDF-файлы и удалить всё остальное:
rm !(*.pdf)
Перед использованием rm полезно проверить, какие файлы подходят под шаблон. Для этого просто замените rm на ls:
ls !(*.pdf)
Так видно, какие именно файлы будут удалены. Это простая проверка перед выполнением опасной команды.
Можно защитить несколько файлов или типов файлов, разделяя шаблоны оператором (|):
rm !(*.pdf|*.conf|README.md)
Эта команда удалит всё, кроме:
- PDF-файлов (
*.pdf) - конфигурационных файлов (
*.conf) - файла
README.md
Постоянное включение extended globbing. Если используете его регулярно, добавьте следующую строку в файл ~/.bashrc:
echo 'shopt -s extglob' >> ~/.bashrc
source ~/.bashrc
Так возможность будет автоматически включаться при запуске новой сессии Bash.
Предупреждение: команды вроде rm !(pattern) удаляют файлы без возможности восстановления. Сначала всегда проверяйте шаблон с помощью ls !(pattern), чтобы убедиться, что он соответствует нужным файлам, и только потом заменяйте ls на rm.
11. Проверка существования каталога
В Bash оператор ! позволяет обратить результат проверки. Типичный пример: проверка отсутствия каталога.
Например:
[ ! -d /home/avi/Tecmint ] && printf "\nNo such directory exists.\n" || printf "\nDirectory exists.\n"
Как это работает:
-dпроверяет, является ли указанный путь каталогом.!обращает результат, поэтому условие истинно только тогда, когда каталога нет.&&выполняет первую команду, если проверка успешна.||выполняет вторую команду, если проверка неуспешна.
Если каталога /home/avi/Tecmint нет, вы увидите:
No such directory exists.
Иначе вывод будет:
Directory exists.
Часто в shell-скриптах так останавливают выполнение, если нужный каталог недоступен.
[ ! -d /home/avi/Tecmint ] && exit 1
Если каталога нет, скрипт завершается с кодом 1.
Другой распространённый подход: автоматически создать каталог.
[ ! -d /home/avi/Tecmint ] && mkdir -p /home/avi/Tecmint
Такая проверка делает скрипт понятнее, но она не обязательна: mkdir -p и так создаёт каталог, только если его нет, и ничего не делает, если он уже существует. Во многих случаях достаточно написать:
mkdir -p /home/avi/Tecmint
Это короче и даёт тот же результат.
12. Инверсия кода завершения команды
Оператор ! можно также поставить перед командой, чтобы обратить её код завершения. Если команда завершилась успешно, ! превратит результат в неуспешный. Если команда упала, ! сделает результат успешным. Такой приём часто применяют в if для более естественной обработки ошибок.
Например:
if ! systemctl is-active --quiet nginx; then
echo "nginx is down, restarting..."
systemctl restart nginx
fi
Что здесь происходит:
systemctl is-active --quiet nginxвозвращает успех, если служба nginx запущена.- Оператор
!обращает этот результат. - Если служба не запущена, условие становится истинным, и выполняются команды из блока then.
Такой скрипт читается проще, чем проверка ошибки через else. Ещё один частый случай: проверить наличие нужной команды до продолжения работы скрипта.
if ! command -v jq >/dev/null 2>&1; then
echo "jq is required but not installed." >&2
exit 1
fi
В этом примере:
command -v jqпроверяет наличие команды jq.- Стандартный вывод и вывод ошибок перенаправляются в
/dev/null. - Если
jqне установлена, скрипт выводит сообщение об ошибке и завершается.
Такое использование ! делает обработку ошибок простой и наглядной.
13. Косвенное раскрытие переменных
В подстановке параметров у символа ! другое значение. Он позволяет использовать значение одной переменной как имя другой. Эта возможность называется косвенным раскрытием.
Например:
nginx_port=8080
service=nginx
echo ${!service}_port
Этот фрагмент не сработает так, как задумано, потому что Bash сначала раскроет ${!service} в значение переменной с именем nginx, которой в данном примере нет.
Правильный способ: сохранить полное имя переменной в другой переменной.
nginx_port=8080
service=nginx_port
echo "${!service}"
Вывод:
8080
Здесь переменная service содержит текст nginx_port, а ${!service} говорит Bash обращаться с этим текстом как с именем переменной и вернуть её значение.
В случае массивов ${!array[@]} возвращает не значения, а индексы массива.
fruits=(apple banana mango)
echo "${!fruits[@]}"
Вывод:
0 1 2
Это особенно полезно при переборе индексированных или ассоциативных массивов. Можно также вывести имена переменных, которые начинаются с заданного префикса.
Например:
echo "${!BASH_*}"
Эта команда выведет имена всех переменных оболочки, которые начинаются с BASH_.
Часто ${!array[@]} используют для перебора ключей ассоциативного массива.
declare -A ports=(
[nginx]=80
[postgres]=5432
[redis]=6379
)
for service in "${!ports[@]}"; do
echo "$service listens on ${ports[$service]}"
done
Вывод:
postgres listens on 5432
nginx listens on 80
redis listens on 6379
Получение ключей ассоциативного массива в Bash обычно делается через ${!array[@]}. Это самый надёжный способ.
14. Исправление ошибки event not found, вызванной символом !
Если команда содержит восклицательный знак (!) внутри двойных кавычек, Bash может принять его за подстановку истории, а не за обычный текст. Тогда появляется ошибка примерно такого вида:
mysql -u admin -p"S3cret!Pass"
Пример вывода:
bash: !Pass: event not found
Это происходит потому, что Bash воспринимает !Pass как ссылку на историю и пытается найти ранее выполненную команду, которая начинается с Pass. Такой команды нет, поэтому выводится ошибка.
Решение 1. Самый простой способ: использовать одинарные кавычки, которые отключают подстановку истории.
mysql -u admin -p'S3cret!Pass'
Одинарные кавычки воспринимают всё внутри буквально, поэтому пароль передаётся ровно в том виде, в каком написан.
Решение 2. Если часто работаете со строками, содержащими !, можно временно отключить подстановку истории.
set +H
OR
set +o histexpand
После завершения включите её снова:
set -H
OR
set -o histexpand
Проблема касается не только паролей. Она может возникать с URL, именами файлов и любым другим текстом с восклицательным знаком. В большинстве случаев проще и безопаснее всего использовать одинарные кавычки, чтобы не получать ошибку event not found.
15. Почему ! не работает в shell-скриптах
Многие варианты использования ! относятся к подстановке истории Bash, которая предназначена только для интерактивных сессий терминала. При запуске Bash-скрипта подстановка истории по умолчанию отключена. Это можно проверить через параметр histexpand:
bash -c 'set -o | grep histexpand'
Вывод:
histexpand off
Поэтому перечисленные ниже сокращения истории не работают внутри shell-скриптов:
!!!$!123!string^old^new^
Эти механизмы зависят от интерактивной истории команд, которая при выполнении скрипта недоступна.
Символ !, который работает в скриптах
Оператор ! для логического отрицания устроен совершенно иначе и нормально работает в shell-скриптах.
Например:
if ! command -v curl >/dev/null 2>&1; then
echo "curl is not installed."
fi
OR
if [ ! -d /home/user/data ]; then
mkdir -p /home/user/data
fi
В этих примерах ! просто обращает результат команды или проверки. Такое поведение работает и в интерактивных оболочках, и в shell-скриптах.
Коротко:
- Подстановка истории (
!!,!$,!number,!string,^old^new^) работает только в интерактивной командной строке Bash. - Логическое отрицание (
! command,[ ! -d dir ]и подобные выражения) работает и в интерактивных оболочках, и в shell-скриптах.
Заключение
Символ ! умеет гораздо больше, чем знакомое многим пользователям Linux сочетание !!. В зависимости от контекста он может вызывать команды из истории, повторно использовать аргументы, исправлять опечатки, инвертировать условия в проверках и даже выполнять косвенное раскрытие переменных.
Важно помнить, что подстановка истории работает только в интерактивной сессии Bash. Если скопировать команду с !! или !$ в shell-скрипт, она не будет работать так же, потому что в неинтерактивных оболочках подстановка истории отключена. В скриптах лучше сохранять значения в переменных, а не полагаться на сокращения истории.
Если разобраться в этих вариантах использования, символ ! поможет быстрее работать в командной строке и писать более чистые и эффективные Bash-скрипты.






Пока нет комментариев. Будьте первым!