Некоторое время поработав в Linux, понабирав команды в командной строке, приходишь к выводу, что в общении с оболочкой не помешают кое-какие удобства. Одно из таких удобств — возможность редактировать вводимую строку с помощью клавиши Backspace (удаление последнего символа), Ctrl+W (удаление слова) и Ctrl+U (удаление всей строки) — предоставляет сам терминал Linux. Эти команды работают для любого построчного ввода в терминале. Если по каким-то причинам в строчку на экране влез мусор, можно нажать Ctrl+R (redraw) — система выведет в новой строке содержимое входного буфера.
Командная оболочка поддерживает некоторые базовые операции по редактированию командной строки, которых можно ожидать для любого текстового ввода. Речь идёт о клавишах Стрелка влево и Стрелка вправо, с помощью которых можно перемещать курсор по командной строке, и клавише Del, удаляющей символ под курсором, а не позади него. Помимо этого перемещаться в командной строке можно не только по одному сиволу вперёд и назад, но и по словам: команды ESCF
/ESCB
или Alt+F
/Alt+B
соответственно (от forward и backward), работают также клавиши Home и End, или, что то же самое, Ctrl+A и Ctrl+E.
Bash располагает весьма мощным механизмом — возможностью работать с историей команд. Все команды, набранные пользователем, bash
запоминает и позволяет обращаться к ним впоследствии. Для работы с историей команд используются клавиши со стрелками — вверх и вниз. По стрелке вверх (можно использовать и Ctrl+P, previous), список поданных команд «прокручивается» от последней к первой, а по стрелке вниз (Ctrl+N, next) — обратно. Соответствующая команда отображается в командной строке как только что набранная, её можно отредактировать и подать оболочке (подгонять курсор к концу строки при этом не обязательно).
Если необходимо добыть из истории какую-то давнюю команду, проще не гонять список истории стрелками, а поискать в ней с помощью команды Ctrl+R (reverse search). При этом выводится подсказка специального вида («(reverse-i-search)»), подстрока поиска (окружённая символами `
и '
) и последняя из команд в истории, в которой эта подстрока присутствует:
[methody@localhost methody]$
^R | (reverse-i-search)`':
i | (reverse-i-search)`i': ls i
n | (reverse-i-search)`in': info
f | (reverse-i-search)`inf': info
o | (reverse-i-search)`info': info
^R | (reverse-i-search)`info': man info
^R | (reverse-i-search)`info': info "(bash.info.bz2)Commands For History"
Пример представляет символы, вводимые пользователем (в левой части до “|
”), и содержимое последней строки терминала. Это «кадры» работы с одной и той же строкой, показывающие, как она меняется при наборе. Набрав «info», пользователь продолжил поиск этой подстроки, повторяя Ctrl+R до тех пор, пока не наткнулся на нужную ему команду, содержащую подстроку “info
”. Осталось только передать её bash
с помощью Enter.
Чтобы история команд могла сохраняться между сеансами работы пользователя, bash
записывает её в файл .bash_history
, находящийся в домашнем каталоге пользователя. Делается это в момент завершения оболочки: накопленная за время работы история дописывается в конец этого файла. При следующем запуске bash
считывает .bash_history
целиком. История хранится не вечно, количество запоминаемых команд в .bash_history
ограничено (обычно 500 командами, но это можно и перенастроить).
Поиск по истории — удобное средство: длинную командную строку можно не набирать целиком, а выискать и использовать. Однако давнюю команду придётся добывать с помощью нескольких Ctrl+R — а можно и совсем не доискаться, если она уже выбыла оттуда. Для того, чтобы оперативно заменять длинные команды короткими, стоит воспользоваться сокращениями (aliases). В конфигурационных файлах командного интерпретатора пользователя обычно уже определено несколько сокращений, список которых можно посмотреть с помощью команды alias
без параметров:
[methody@localhost methody]$ alias
alias cd..='cd ..'
alias cp='cp -i'
alias l='ls -lapt'
alias ll='ls -laptc'
alias ls='ls --color=auto'
alias md='mkdir'
alias mv='mv -i'
alias rd='rmdir'
alias rm='rm -i'
Выяснилось, что по команде ls
вместо утилиты /bin/ls
bash
запускает собственную команду-сокращение, превращающееся в команду ls --color=auto
. Повторно появившуюся в команде подстроку “ls
” интерпретатор уже не обрабатывает, во избежание вечного цикла. Например, команда ls -al
превращается в результате в ls --color=auto -al
. Точно так же любая команда, начинающаяся с rm
, превращается в rm -i
(interactive), в результате чего ни одно удаление не обходится без вопросов в стиле «rm: удалить обычный файл `файл'
?». Избавиться от ненужного сокращения можно с помощью команды unalias
.
Сокращения позволяют быстро набирать команды, однако никак не затрагивают имён файлов, которые чаще всего и оказываются параметрами этих команд. Бывает, что набранной строки — пути к файлу и нескольких первых букв его имени — достаточно для однозначного указания на этот файл, потому что по введённому пути болшьше файлов, чьё имя начинается на эти буквы, просто нет. Чтобы не дописывать оставшиеся буквы в bash
можно нажать клавишу Tab. И bash
сам достроит имя файла до полного (снова воспользуемся методом «кадров»):
[methody@localhost methody]$ ls -al /bin/base
Tab | [methody@localhost methody]$ ls -al /bin/basename
-rwxr-xr-x 1 root root 12520 Июн 3 18:29 /bin/basename
[methody@localhost methody]$ base
Tab | [methody@localhost methody]$ basename
Tab | [methody@localhost methody]$ basename ex
Tab | [methody@localhost methody]$ basename examples/
Tab | [methody@localhost methody]$ basename examples/sample-file
sample-file
Дальше — больше. Оказывается, и имя команды можно вводить не целиком: оболочка догадается достроить набираемое слово именно до команды, раз уж это слово стоит в начале командной строки. Таким образом, команда basename examples/sample-file
была набрана за восемь нажатий клавиш («base» и четыре Tab)! Не потребовалось вводить начало имени файла в каталоге examples
, потому что файл там был всего один.
Выполняя достраивание (completion), bash
может вывести не всю строку, а только ту её часть, относительно которой у него нет сомнений. Если дальнейшее достраиване может пойти несколькими путями, то однократное нажатие Tab приведёт к тому, что bash
растерянно пискнет1, а повторное — к выводу под командной строкой списка всех возможных вариантов. В этом случае надо подсказать командной оболочке продолжение: дописать несколько символов, определяющих, по какому пути пойдёт достраивание, и снова нажать Tab.
Дополнения в bash
находятся ещё не на самой вершине удобства и экономии нажатий на клавиши. Если в bash
несколько типов достраивания (по именам файлов, по именам команд и т. п.), то в zsh
их сколько угодно: существует способ запрограммировать любой алгоритм достраивания и задать шаблон командной строки, в которой именно этот способ будет применяться.
Достраивание очень удобно, когда цель пользователя — задать один конкретный файл в командной строке. Если же нужно работать сразу с несколькими файлами — например для перемещения их в другой каталог с помощью mv
, достраивание не помогает. Необходим способ задать одно «общее» имя, которое будет описывать сразу группу файлов, с которыми будет работать команда. В подавляющем большинстве случаев это можно сделать при помощи шаблона.
Символы в шаблоне разделяются на обычные и специальные. Обычные символы означают сами себя, а специальные обрабатываются особым образом:
abc
” соответствует строка abc
, но не aBc
или ABC
, потому что большие и маленькие буквы различаются.*
”, соответствует любая строка любой длины (в том числе и пустая).?
”, соответствует любая строка длиной в один символ, например, a
, +
или @
, но не ab
или 8888
.[
” и “]
” соответствует строка длиной в один символ, причём этот символ должен встречаться среди заключённых в скобки. Например, шаблону “[bar]
” соответствуют только строки a
, b
и r
, но не c
, B
, bar
или ab
. Символы внутри скобок можно не перечислять полностью, а задавать диапазон, в начале которого стоит символ с наименьшим ASCII-кодом, затем следует “-
”, а затем — символ с наибольшим ASCII-кодом. Например, шаблону “[0-9a-fA-F]
” соответствует одна шестнадцатеричная цифра (скажем, 5
, e
или C
). Если после “[
” в шаблоне следует “!
”, то ему соответствует строка из одного символа не перечисленного между скобками.a*b?c
” будут соответствовать строки ab@c
(“*
” соответвтует пустая подстрока), a+b=c
и aaabbc
, но не соответствовать abc
(“?
” соответвтует подстрока c
, а для “c
” соответствия не находится), @ab@c
(нет соответствия для “a
”) или aaabbbc
(из трёх b
превое соответствует “b
”, второе — “?
”, а вот третье приходится на “c
”).Шаблоны используются в нескольких конструкциях shell. Главное место их применения — командная строка. Если оболочка видит в командной строке шаблон, она немедленно заменяет его на список файлов, имена которых ему соответствуют. Команда, которая затем вызывается, получает в качестве параметров список файлов уже безо всяких шаблонов, как если бы этот список пользователь ввёл вручную. Эта способность командного интерпретатора называется генерацией имён файлов.
[methody@localhost methody]$ ls .bash*
.bash_history .bash_logout .bash_profile .bashrc
[methody@localhost methody]$ /bin/e*
/bin/ed /bin/egrep /bin/ex
[methody@localhost methody]$ ls *a*
sample-file
[methody@localhost methody]$ ls -dF *[ao]*
Documents/ examples/ loop to.sort*
При использовании шаблонов новичок может натолкнуться на несколько «подводных камней». В приведённом примере только первая команда срабатывает не вопреки ожиданиям: шаблон “.bash*
” был превращён командной оболочкой в список файлов, начинающихся на .bash
, этот список получила в качестве параметров командной строки утилита ls
, после чего честно его вывела. “/bin/e*
” — это на самом деле опасная команда, с которой в данном случае просто повезло: этот шаблон превратился в список файлов из каталога /bin
, начинающихся на “e
”, и первым файлом в списке оказалась безобидная утилита /bin/echo
. Поскольку в командной строке ничего, кроме шаблона, не было, именно строка /bin/echo
была воспринята оболочкой в качестве команды, которой — в качестве параметров — были переданы остальные элементы списка — /bin/ed
, /bin/egrep
и /bin/ex
.
Что же касается ls *a*
, то кажется, что эта команда должна была выдать список файлов в текущем каталоге, имя которых содержит “a
”. Вместо этого на экран вывелось имя файла из подкаталога examples
... Впрочем, никакой чёрной магии тут нет. Во-первых, имена файлов вида “.bash*
” хотя и содержат “a
”, но начинаются на точку, и, стало быть, считаются скрытыми. Скрытые файлы попадают в результат генерации имён только если точка в начале указана явно (как в первой команде примера). Поэтому по шаблону “*a*
” в домашнем каталоге bash
нашёл только подкаталог с именем examples
, его-то он и передал в качестве параметра утилите ls
. Что вывелось на экран в результате образовавшейся команды ls examples
? Конечно, содержимое каталога. Шаблон в последней команде из примера, “*[ao]*
”, был превращён в список файлов, чьи имена содержат “a
” или “o
” — Documents examples loop to.sort
, а ключ “-d
” потребовал у ls
показывать информацию о каталогах, а не об их содержимом. В соответствии с ключом “-F
”, ls
расставил “/
” после каталогов и “*
” после исполняемых файлов.
Ещё одно отличие генерации имён от стандартной обработки шаблона — в том, что символ “/
”, разделяющий элементы пути, никогда не ставится в соответствие “*
” или диапазону. Происходит это не потому, что искажён алгоритм, а потому, что при генерации имён шаблон применяется именно к элементу пути, внутри которого уже нет “/
”. Например, получить список файлов, которые находятся в каталогах /usr/bin
и /usr/sbin
и содержат подстроку “ppp
” в имени, можно с помощью шаблона “/usr/*bin/*ppp*
”. Однако одного шаблона, который бы включал в этот список ещё и каталоги /bin
и /sbin
— то есть подкаталоги другого уровня вложенности — по стандартным правилам сделать нельзя2.
Если перед любым специальным символом стоит “\
”, этот символ лишается специального значения, экранируется: пара “\символ
” заменяется командным интерпретатором на “символ
” и передаётся в командную строку безо всякой дальнейшей обработки:
[methody@localhost methody]$ echo *o*
Documents loop to.sort
[methody@localhost methody]$ echo \*o\*
*o*
[methody@localhost methody]$ echo "*o*"
*o*
[methody@localhost methody]$ echo *y*
*y*
[methody@localhost methody]$ ls *y*
ls: *y*: No such file or directory
Обратите внимание, что шаблон, которому не соответствует ни одного имени файла, bash
раскрывать не стал, как если бы все “*
” в нём были экранированы. В самом деле, какое из двух зол меньшее: изменять интерпретацию спецсимволов в зависимости от содержимого каталога, или сохранять логику интерпретации с риском превратить команду с параметрами в команду без параметров? Если бы, допустим, шаблон, которому не нашлось соответствия, попросту удалялся, то команда ls *y*
превратилась бы в ls
и неожиданно выдала бы содержимое всего каталога. Авторы bash
(как и Стивен Борн, автор самой первой командной оболочки — sh
) выбрали более непоследовательный, но и более безопасный первый способ3.
Лишить специальные символы их специального значения можно и другим способом. Разделители (пробелы, символы табуляции и символы перевода строки) перестают восприниматься таковыми, если часть командной строки, их содержащую, окружить двойными или одинарными кавычками. В кавычках престаёт «работать» и генерация имён (как это видно из примера), и интерпретация других специальных символов. Двойные кавычки, однако, допускают выполнение подстановок переменной окружения и результата работы команды.
1Все терминалы должны уметь выдавать звуковой сигнал при выводе управляющего символа Ctrl+G. Для этого не нужно запускать никаких дополнительных программ: «настоящие» терминалы имеют встроенный динамик, а виртуальные консоли обычно пользуются системным («пищалкой»). В крайнем случае разрешается привлекать внимание пользователя другими способами: например, эмулятор терминала screen
пишет в служебной строке «wuff-wuff» («гав-гав»).
2Генерация имён файлов в zsh
предусматривает специальный шаблон “**
”, которому соответствуют подстроки с любым количеством “/
”. Пользоваться им следует крайне осторожно, понимая, что при генерации имён по такому шаблону выполняется операция, аналогичная не ls
, а ls -R
или find
. Так, использование “/**
” в начале шаблона вызовет просмотр всей файловой системы!
3Авторы zsh
пошли по другому пути: в этой версии shell использование шаблона, которому не соответствует ни одно имя файла, приводит к ошибке, и соответствующая команда не выполняется.