Практическое написание сценариев командной оболочки Bash/Приложения

← Код-сниппеты Глава
Приложения


Получение справкиПравить

Обычно вместе с Bash устанавливаются и man-страницы. Убедиться в том, что они есть в вашей системе можно командой

$ man -k bash

bash (1)             - GNU Bourne-Again SHell
bash-builtins (7)    - bash built-in commands, see bash(1)
bashbug (1)          - report a bug in bash
builtins (7)         - bash built-in commands, see bash(1)
dh_bash-completion (1) - install bash completions for package
rbash (1)            - restricted bash, see bash(1)

Число в скобках означает раздел команды man, в котором хранится указанная страница. Например справка по встроенным командам (builtins (7)) хранится в 7-ом разделе. Чтобы ее открыть нужно ввести

$ man bash-builtins
# или
$ man 7 bash-builtins

Получение цветовой схемы терминалаПравить

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

\e[##m

где вместо ## нужно подставить определенные коды, отвечающие за отображение.

Символы \e и [ начинают любую управляющую последовательность, а сама пара называется CSI (от Control Sequence Introducer). Вместо \e допустимо записывать \033, т.е.

\033[##m

однако так запись кажется не такой опрятной.

Следующие коды отвечают за отображение любого текста, следующего за ними:

   \e[0m        # Сброс отображения к значению по умолчанию
   \e[1m        # Полужирное начертание
   \e[2m        # Приглушенный цвет
   \e[4m        # Яркий цвет
   \e[5m        # Мигание
   \e[7m        # Инвертирует фон литеры с ее цветом, например белый цвет на черном фоне инвертируется в черный цвет на белом фоне
   \e[22m       # Установить нормальную интенсивность
   \e[24m       # Убрать подчеркивание
   \e[25m       # Убрать мигание
   \e[27m       # Отменить реверсивное начертание
   \e[30m       # Черный цвет для символа
   \e[31m       # Красный цвет для символа
   \e[32m       # Зеленый цвет для символа
   \e[33m       # Желтый цвет для символа
   \e[34m       # Синий цвет для символа
   \e[35m       # Фиолетовый цвет для символа
   \e[36m       # Голубой цвет для символа
   \e[37m       # Серый цвет для символа
   \e[39m       # Сбросить цвет символа к значению по умолчанию
   \e[40m       # Черный фон для символа
   \e[41m       # Красный фон для символа
   \e[42m       # Зеленый фон для символа
   \e[43m       # Желтый фон для символа
   \e[44m       # Синий фон для символа
   \e[45m       # Фиолетовый фон для символа
   \e[46m       # Голубой фон для символа
   \e[47m       # Серый фон для символа
   \e[49m       # Сбросить фон символа к значению по умолчанию

С полным списком управляющих последовательностей ANSI вы можете познакомиться на следующей странице.

Вы можете в любой момент поэкспериментировать с управляющими последовательностями, например так

echo -e "\e[7mTest text\e[0m Text after"
# Выведет Test text с инверсией цветов (черный на белом)

# За один раз можно использовать несколько последовательностей
echo -e "\e[7m\e[36mTest text\e[0m Text after"
# Выведет Test text с инверсией цветов (черный на голубом)

Вы можете обойтись одним CSI, если будете перечислять коды через точку с запятой. Коды будут применяться именно в том порядке, в котором следуют.

echo -e "\e[33;42;1mTest text\e[0m Text after"
# Выведет полужирный (1) желтый цвет (33) на зеленом фоне (42).

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

echo
for x in 0 1 4 5 7 8; do
    for i in $(seq 30 37); do
        for a in $(seq 40 47); do
            echo -ne "\e[$x;$i;$a""m\\\e[$x;$i;$a""m\e[0;37;40m "
        done; echo
    done
done
echo
 
Пример интерпретации управляющих последовательностей в эмуляторе терминала xterm

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

   \e[0;48;5;NNNm    # для инвертированного цвета
   \e[0;38;5;NNNm    # для нормального цвета

# где вместо NNN нужно использовать код от 0 до 255. Например, \e[0;48;5;16m это инверсия (белый на черном).

# Сброс осуществляется все тем же \e[0m.

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

echo  
for i in $(seq 0 255); do
    printf "\e[0;48;5;${i}m %03d\e[0;38;5;${i}m %03d " $i $i
    [[ $i -eq 7 || $i -eq 15 || $i -eq 231 || $i -eq 239 || $i -eq 247 ]] && echo
    [[ $i -ge 15 && $i -le 231 && $(( ($i - 15) % 6 )) -eq 0 ]] && echo
done
echo
 
Пример интерпретации управляющих последовательностей в эмуляторе терминала xterm

Горячие клавиши командной оболочкиПравить

Замечания
  • Перечисляемые здесь горячие клавиши могут не работать в вашей системе по той причине, что они не настроены или настроены по-другому. Обычно сочетания горячих клавиш хранятся в файле .inputrc домашней директории пользователя. Если этого файла нет, то просматривается файл /etc/inputrc. Для ознакомления со структурой этого файла обратитесь к официальной документации. Чтобы посмотреть какие клавиши сейчас настроены, используйте команду bind -P.
  • В официальной документации можно встретить такую нотацию по горячим клавишам, которая не понятна, если читатель столкнулся с документацией в *nix впервые:
    • Любое сочетание, описываемое как С-x (где x любая клавиша на клавиатуре), означает Ctrl + x. На некоторых страницах в том же значении оно может быть выражено как ^x.
    • Любое сочетание, описываемое как M-x (где x любая клавиша на клавиатуре), означает Alt + x. M означает в этом контексте Meta. Это обусловлено историческими причинами: старые клавиатуры действительно имели клавишу Meta, которая имела наклейку в виде ромбика. В современных клавиатурах эта клавиша отсутствует и может эмулироваться клавишой Alt / ⊞ Win / ⌥ Option в зависимости от настроек клавиатуры.
  • На старых клавиатурах не было клавиш со стрелками, поэтому вас может удивить то, что в документации движение курсора настроено на буквы, а про стрелки вообще не сказано ни слова. В современных дистрибутивах движение курсора по стрелкам работает.
  • По умолчанию Bash использует сочетания клавиш, пришедшие из редактора Emacs. Существует опция, позволяющая переключить комбинацию на схему, используемую в vi, но так как практически большинство современных разработчиков знает именно сочетания Emacs, вряд ли вы захотите это сделать.

Перемещение каретки в интерактивном режимеПравить

Позиция каретки на строке

Сочетание Что происходит Позиция курсора до Позиция курсора после

Ctrl+ или
 Alt+f

Движение курсора вперед на одно слово (если в строке есть слеш, то разделителем слов будет считаться слеш) $ less /etc/hosts $ less /etc/hosts

Ctrl+ или
 Alt+b

Движение курсора назад на одно слово (если в строке есть слеш, то курсор переместиться на первую позицию после него) $ less /etc/hosts  $ less /etc/hosts

Ctrl+e

Переместить курсор в конец строки $ less /etc/hosts $ less /etc/hosts 

Ctrl+a

Переместить курсор в начало строки $ less /etc/hosts  $ less /etc/hosts

Ctrl+f

Переместить курсор на один символ вперед $ less /etc/hosts $ less /etc/hosts

Ctrl+b

Переместить курсор на один символ назад $ less /etc/hosts $ less /etc/hosts

Работа с символами под курсоромПравить

Сочетание Что происходит Позиция курсора до Позиция курсора после

Ctrl+d

Удаляет символ под курсором $ less /etc/hosts $ ess/etc/hosts

Ctrl+k

Удалить все символы начиная с текущего и до конца строки $ less /etc/hosts $ less 

Ctrl+t

Поменять текущий символ с впередистоящим местами $ elss /etc/hosts $ less /etc/hosts

Ctrl+u

Удалить все символы, начиная с предстоящего от текущего и до начала строки $ less /etc/hosts $  /etc/hosts

Ctrl+w или
 Alt+← Backspace

Удалить впередистоящее слово (слеши при этом разделителями слов не считаются) $ less /etc/hosts  $ less  

Alt+l

Перевести символы всего следующего слова от курсора в нижний регистр $ LESS /etc/hosts $ less /etc/hosts

Alt+u

Перевести символы всего следующего слова от курсора в верхний регистр $ echo HELLO world $ echo HELLO WORLD 

Alt+c

Переводит первый символ слова в верхний регистр, а все остальные в нижний $ echo HELLO world $ echo Hello world

Ctrl+l

Очистить экран подобно команде clear
Примечание
Сочетание Ctrl+w интерпретируется терминальным устройством, а не оболочкой, но сюда оно вставлено, потому что так логично для изложения.

Работа с текущей строкойПравить

Знакомое вам сочетание «копировать-вставить» в Bash не работает (если эмулятор терминала его не поддерживает), потому что в те далекие времена концепции буфера обмена еще не было (по крайней мере, в той форме, в которой это понимается сейчас).

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

Автодополнение мы рассмотрим чуть позже. Для вырезания фрагмента вы можете воспользоваться одной из комбинаций, о которых мы уже упомянули выше:

  • Ctrl+w — вырезать впереди стоящее от курсора слово;
  • Ctrl+u — вырезать весь впереди стоящий фрагмент строки относительно курсора;
  • Ctrl+k — вырезать весь фрагмент от курсора и до конца строки.

Вырезанное можно затем вставить комбинацией Ctrl+y. Вы можете вставить вырезанный фрагмент сколько угодно раз.

В Bash реализован круговой буфер: если вы много вырезали, то все варианты до поры до времени будут хранится в буфере. Вы можете перебирать вырезанные варианты из буфера комбинацией Alt+y.

Если вы ошиблись в своем предыдущем действии, то его можно отменить комбинацией Ctrl+⇧ Shift+_, либо вы можете вырезать фрагмент и повторить ввод.

Для длинных команд вы можете воспользоваться редактором. В старых версиях Bash редатор указывался в переменной окружения EDITOR (обычно эта переменная не определена, но вы можете определить ее в любое время задав конкретный редактор в форме пути или команды. Эта переменная проверяется в первую очередь). В современных версиях Bash используется редактор по символической ссылке, возвращаемой командой which editor.

Чтобы вызвать редактор, воспользуйтесь двойным сочетанием Ctrl+x, Ctrl+e. Bash переключится в некоторый редактор (например, vi), где вы можете ввести команду, пользуясь всеми удобствами редактора. Затем вам нужно выйти сохранив файл под именем, предложенным Bash. После этого Bash выполнит все команды из этого файла. Помните, что каждая строка файла интерпретируется как отдельный ввод.

Есть еще одно интересное сочетание, позволяющее закомментировать текущую введенную строку: Alt+⇧ Shift+#. Строка будет закомментирована, после чего автоматически будет введен ↵ Enter. Данная команда сохранится в истории команд, поэтому вы сможете ей воспользоваться позже, найдя ее в истории. Эта процедура напоминает чем-то копирование, где хранилищем выступает история.

Управляющие комбинации терминального устройстваПравить

Терминал может интерпретировать сочетания клавиш, начинающиеся на Ctrl, и в зависимости от настроек, будет сгенерирован соответствующий сигнал, посылаемый в командную оболочку.

Чтобы посмотреть настройку текущего терминального устройства, используйте следующую команду

$ stty -a
speed 38400 baud; rows 30; columns 120; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?; swtch = M-^?; start = ^Q; stop = ^S;
susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 hupcl -cstopb cread -clocal -crtscts
-ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc ixany imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc

Ниже перечислены наиболее часто используемые сигналы, которые можно послать с клавиатуры.

Сигнал Сочетание Пояснение
SIGINT Ctrl+c Обычно этот сигнал прерывает исполнение текущей команды оболочки
SIGTSTP Ctrl+z Обычно приостанавливает (ставит на паузу) выполнение задачи, выполняемой на переднем плане. Останавливаемая задача уходит на задний план. Чтобы продолжить ее на переднем плане, нужно использовать команду fg; чтобы продолжить ее на заднем плане — bg

Прочие управляющие последовательности представлены в таблице ниже.

Сочетание Пояснение
Ctrl+d Посылает в стандартный поток ввода текущей команды командной оболочки символ EOF (End of File). Сама оболочка Bash интерпретирует этот символ как завершение текущего сеанса пользователя. Другие программы могут интерпретировать этот символ по-разному, но обычно этот символ сигнализирует, что поток ввода окончен и можно выйти. Так делает например команда cat
Ctrl+v Позволяет сказать терминалу, что следующий вводимый символ нужно интерпретировать дословно. Очень наглядно это выглядит по нажатию клавиши Tab ↹. В обычных условиях командная оболочка перехватывает эту клавишу у терминала и запускает механизм автодополнения команды, но можно перехватить нажатие клавиши раньше на уровне терминала, тогда символ по этой клавише будет интерпретирован дословно самим терминалом. Например, если вы введете Ctrl+v, Tab ↹, то терминал будет интерпретировать управляющую последовательность \t, т.е. на экране появится смещение от табуляции

История командПравить

Все команды, которые вы вводили под своей учетной записью, сохраняются в файл истории ~/.bash_history. Местонахождение этого файла вы сможете определить через вывод значения переменной HISTFILE. Каждая строка в файле это отдельная запись. Таких записей не может быть больше значения переменной HISTSIZE, которая обычно определяется в ~/.bashrc. В историю попадают не все команды, другими словами Bash использует параметр HISTCONTROL для отсеивания. Обычно он установлен в значение ignoreboth, что означает не фиксировать в истории команды, которые уже есть в истории, и не фиксировать пустые строки.

Основные приемы работы с историейПравить

  • Чтобы вывести весь файл истории, используется команда history. Если указать число, то выведет последние несколько команд, в соответствии с числом.
  • Чтобы подставить последнюю команду из истории, используется строка из двух восклицательных знаков (!!). Часто используемый сценарий применения этой возможности, это когда вы пытаетесь выполнить действие, требующее привилегий суперпользователя, но забыли ввести команду sudo. Следующие пример демонстрирует это.
    $ vi /etc/resolv.conf
    bash: /etc/resolv.conf permission denied
    
    $ sudo !! # Результат: sudo vi /etc/resolv.conf
    
  • Если вы знаете номер команды истории относительно последней, то можно воспользоваться номером. Так !-1 выведет последнюю команду истории на текущий момент, !-2 — предпоследнюю и так далее.
  • Если после восклицательного знака ввести несколько первых символов команды, то оболочка попытается найти первую совпадающую команду, начиная с конца.
    $ vi /etc/resolv.conf
    $ vi /etc/ssh/sshd_config
    
    $ !vi   # Ближайшая похожая команда vi /etc/ssh/sshd_config
    
  • Историю команд можно листать по клавишам со стрелками и . Если настроено, это также могут быть PageUp и PageDown.
Примечание
Для того чтобы PageUp и PageDown начали работать, нужно привязать их к функциям в файле inputrc следующим образом:
"\e[5~": history-search-backward
"\e[6~": history-search-forward
  • Можно брать аргументы предыдущей команды с помощью следующих переменных:
    • !!:$ или !:$ — взять последний аргумент предыдущей команды.
    • !!:0 или !:0 — взять первое слово предыдущей команды (обратите внимание, не аргумент, а именно слово). Обычно первым словом и является предыдущая команда.
    • !!:1 или !:1 — взять второе слово предыдущей команды.
    • !!:2 или !:2 — взять третье слово предыдущей команды.
    • и так далее
  • Предыдущий прием можно комбинировать с поиском первой похожей команды, если вместо второго восклицательного знака написать эту команду
    $ less /etc/ssh/sshd_config /etc/resolv.conf
    
    $ less !less:1 # Возьмет первый аргумент предыдущей команды, т.е.
                   # /etc/ssh/sshd_config
    
  • Историю можно очистить командой history -c. Удалить конкретную строку из истории можно командой history -d <номер строки>.
Примечание
Последний аргумент предыдущей команды также может быть вставлен через комбинацию Alt+⇧ Shift+_. Номер аргумента с конца можно указать, если сначала ввести комбинацию Alt+⇧ Shift+- (без ⇧ Shift можно обойтись, если использовать малую цифровую клавиатуру), затем нужно ввести номер и нажать комбинацию Alt+⇧ Shift+_.

Повторное выполнение фрагмента из историиПравить

Для повторного выполнения фрагмента истории вы должны перейти на желаемую команду с помощью стрелок или любым другим путем, а затем последовательно нажимать Ctrl+o. С каждым нажатием будет выполняться команда из истории, после чего на ее место будет вставать следующая команда.

Поиск по историиПравить

Когда в истории может хранится около 1000 записей, листать ее вручную становится проблематичным.

Основным методом поиска по истории является комбинация Ctrl+r, после которого командная оболочка предложит вам начать печатать первые несколько символов команды, которую вы когда-то вводили. По мере того как вы уточняете команду, вам будет предлагаться все более конкретный результат.

Если вы вводили одну и ту же команду несколько раз, но с разными аргументами, вы можете ввести общее начало, а затем повторно нажимая Ctrl+r, поискать команду с конкретными аргументами среди найденного подмножества. Ctrl+r ищет по истории в обратном направлении, а Ctrl+s в прямом направлении. Конкретно комбинация Ctrl+s может перехватываться терминалом, в котором эта же комбинация клавиш отвечает за остановку терминала, поэтому вероятнее всего Ctrl+s работать не будет без дополнительных настроек.

Чтобы сочетание Ctrl+s начало работать, вам нужно опустить специальный флаг терминального устройства IXON, который разрешает ручное управление потоком данных на устройство (т.е. сочетания Ctrl+s для остановки и Ctrl+q для продолжения):

stty -ixon

Для того чтобы закрепить эту настройку, необходимо в файл ~/.bashrc добавить (или скорректировать) примерно такой фрагмент:

case $- in                                     # Запросить опции оболочки, которые сейчас включены
  *i*) stty -ixon <... другие опции ...>  ;;   # Опция i говорит, что оболочка работает в интерактивном режиме
  ...
esac
Примечание
Если после нажатия Ctrl+s у вас остановился терминал, то чтобы продолжить работу нажмите Ctrl+q. Некоторые эмуляторы терминалов перехватывают это сочетание, но игнорируют его из-за по умолчанию отключенного флага IXOFF.

Если вы нашли нужную вам команду, вы можете нажать ↵ Enter, чтобы исполнить ее, либо Ctrl+j, чтобы остановить поиск и просто ввести найденную команду в оболочку (например, чтобы немного ее отредактировать). Если вы передумали что-либо искать, то нажмите Ctrl+g для отмены поиска, при этом ранее введенные символы будут восстановлены.

Остановить поиск можно также с помощью клавиш со стрелками.

Результат последнего поиска будет запомнен, а найденная строка становится текущей в истории.

АвтодополнениеПравить

Bash умеет автодополнять команды в зависимости от контекста. Инструкции для функции автодополнения обычно передаются через ~/.bashrc. Минимально Bash умеет дополнять известные системные команды, пути к файлам и каталогам и дополнять имена объявленных переменных окружения.

Активизировать механизм автодополнения можно:

  • Нажав Tab ↹.
  • Нажав дважды Esc.
  • Нажав Ctrl+i.

Если вариантов автодополнения много, то по первому нажатию будет написана общая часть из подмножества совпадений. По второму нажатию будут предложены все найденные варианты.

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

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

Для простого автодополнения переменной окружения нужно просто начать строку с символа доллара ($). Также Bash разрешает тильда-подстановки, если строка начинается на тильду (~), например, чтобы быстро ввести домашний каталог пользователя.

Также Bash умеет читать некоторые конфигурационные файлы, чтобы вы могли быстро подставлять данные из них. Ниже показаны приемы для получения такого рода информации.

Важно
Следует обратить внимание на одну особенность, которая подробно в документации не объясняется, и наталкиваясь на нее, новички не всегда понимают как следует нажимать клавиши.
Возьмем к примеру комбинацию, которая в документации записана как C-x~. По этой комбинации Bash выведет список всех пользователей системы. Здесь написаны три клавиши, но на самом деле здесь нужно нажать последовательно две комбинации: Ctrl+x, ⇧ Shift+~, так как на клавише с тильдой есть еще символ апострофа. Другими словами, в документации передается какой символ нужно ввести, и если для символа нужна клавиша ⇧ Shift, то это подразумевается.
Допустим вы посмотрели список пользователей и захотели ввести нужного вам пользователя в команду аргументом. Для этого вы должны ввести хотя бы несколько начальных символов, чтобы имя однозначно сопоставлялось, а затем, согласно документации, нажать M-~. Опять же клавиша ⇧ Shift здесь подразумевается, т.е. полная последовательность Alt+⇧ Shift+~.
Ниже мы будем везде подписывать ⇧ Shift для большей ясности, какие клавиши нажимаются.
Данные Сочетание, чтобы посмотреть все варианты Сочетание, чтобы дополнить ввод
Имя пользователя системы Ctrl+x, ⇧ Shift+~ Alt+⇧ Shift+~ или
Esc+⇧ Shift+~
FQDN из /etc/hosts Ctrl+x, ⇧ Shift+@ Alt+⇧ Shift+@ или
Esc+⇧ Shift+@
Все переменные окружения сеанса Ctrl+x, ⇧ Shift+$ Alt+⇧ Shift+$ или
Esc+⇧ Shift+$
Список всех команд, видимых командной оболочке, в том числе их псевдонимы Ctrl+x, ⇧ Shift+! Alt+⇧ Shift+! или
Esc+⇧ Shift+!
Список файлов в текущем каталоге Ctrl+x, ⇧ Shift+\ | /

(Для этой комбинации нужно вводить слеш повернутый направо, т.е. написана клавиша, а не символ. Без ⇧ Shift слеш может быть введен с малой цифровой клавиатуры или по клавише с вопросом / ? ;)
Alt+⇧ Shift+\ | / или
Esc+⇧ Shift+\ | /

(Для этой комбинации нужно вводить слеш повернутый направо, т.е. написана клавиша, а не символ. Без ⇧ Shift слеш может быть введен с малой цифровой клавиатуры или по клавише с вопросом / ? ;)

Находясь в некоторой директории, вы можете подставить в команду сразу все файлы в этой директории, либо, если прописана часть префикса некоторой директории, то указанной директории. Для этого нужно нажать Alt+⇧ Shift+* или Ctrl+x, ⇧ Shift+*. Вообще, последнее сочетание просит разрешить glob-шаблон, поэтому вы можете вводить маскирующие символы в строку перед нажатием сочетания.

Символ звездочки может быть введен без ⇧ Shift через малую цифровую клавиатуру.

Создание правила автодополненияПравить

Для создания правил автодополнения в Bash предусмотрены две встроенные команды:

  • complete — регистрирует правило автодополнения для некоторой команды, которое будет действовать в течение всего сеанса Bash.
  • compgen — генерирует возможные варианты на основе того, что пользователь уже ввел в командную строку.

Вы можете объявить правила дополнения в любой момент загрузки Bash, но в самом простом случае это обычно делается через добавление сценария с правилом в каталог /etc/bash_completion.d.

Существует два подхода к объявлению правил:

  • Статическое правило. Такое правило самое простое и применяется, когда у команды 2-3 варианта вызова без сложных опций. В таком правиле просто перечисляются ключевые слова команды, которые могут следовать за ее именем.
  • Динамическое правило. Динамическое правило подразумевает написание функции, которая будет генерировать строки в зависимости от контекста. Например, если в некоторой команде есть опция с аргументом, которая подразумевает путь до файла, то после имени опции функция должна генерировать строки с файлами относительно текущего рабочего каталога.

Для демонстрации мы будем использовать несуществующую команду dummy. Для начала попробуем зарегистрировать статическое правило. Для нашей команды мы будем поддерживать следующий синтаксис:

dummy ---+-- create -----+--<
         +-- view--------+
         \-- remove------/

Данная запись означает, что у команды есть 3 ключевых слова. Правило для этого случая будет таким:

complete -W "create view remove" dummy

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

complete -r dummy

После регистрации правила попробуйте ввести команду dummy (именно ввести, потому что ее нет в системе), пробел, а затем нажать дважды Tab ↹:

# Bash будет предлагать варианты
$ dummy 
create  remove  view

# Если начать любое из ключевых слов, а затем нажать Tab, то слово будет автоматически дополнено.

Можно вместо ключевых слов закрепить процедуру, которая будет исполняться при вызове автодополнения, но это еще не динамическое правило. К сожалению это не комбинируется с опцией -W, т.е. за один раз можно использовать что-то одно:

complete -C 'printf "create <filename>"' dummy

# Теперь если вы введете 'dummy ' + <Tab>, будет введена целая строчка
$ dummy create <filename>

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

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

complete -A user dummy # Опция -A говорит, какую внутреннюю функцию задействовать для автоматической подстановки.

# В этом примере это имена зарегистрированных пользователей в системе. У команды есть и другие функции автодополнения, о чем
# следует читать в документации bash-builtins (7).
#
# Теперь если нажать Tab дважды, система автодополнения выведет всех пользователей.
$ dummy 
_apt              games             landscape         man               pollinate         sync              systemd-resolve   uucp
backup            gnats             list              messagebus        proxy             sys               systemd-timesync  uuidd
bin               john              lp                news              root              syslog            tcpdump           www-data
daemon            irc               mail              nobody            sshd              systemd-network   tss

Перейдем к динамическим правилам. Динамические правила всегда оформляются отдельной функцией. Задачей функции является анализ уже введенной строки с последующим дополнением ее новыми словами в зависимости от синтаксиса команды. Для анализа введенных слов используется пара предопределенных массивов: COMP_WORDS (массив с уже введенными словами) и COMPREPLY (массив с заменяемыми словами). Весь смысл в том, что вы на основе уже введенных слов из COMP_WORDS формируете новый набор, который возвращаете в COMPREPLY и который развернется в командной строке. Последнее введенное слово находится в самом конце COMP_WORDS и на него можно сослаться через переменную-счетчик с именем COMP_CWORD.

Пусть команда dummy имеет такой синтаксис

 dummy -+- work ----<  /- export -- <string> -- (--src) -- <source-dir> -- (--file) -- <history-file> --<
        +- history ----+- import -- <string> -- (-o) -- <output-file> --<
        |           
        \- help ----+- work -+--+--<
                    |- history--|
                    \- help ----/

Файл с динамическим правилом в таком случае может иметь следующий вид.

# Файл с правилом обычно не имеет башенг, потому что он включается в сеанс инструкцией source.

# Имена для динамических правил всегда следует начинать на нижнее подчеркивание (и должны быть уникальными), так как они будут видны на протяжении
# всего сеанса командной оболочки.
_dummy() {
    COMPREPLY=()  # этот массив используется, если вы регистрируете метод через опцию -F.
    
    local options_level1="work history help"
    local options_history="import export"
    local options_help="work history help"
    local options_import="--src --file"
    
    local current="${COMP_WORDS[COMP_CWORD]}"  # Последнее введенное слово в строке команды

    # На практике удобнее всего определить переменные так, но в этом примере мы так не делаем для наглядности.
    # 
    #  local cur_word prev_word
    #  cur_word="${COMP_WORDS[COMP_CWORD]}"
    #  prev_word="${COMP_WORDS[COMP_CWORD-1]}"
    #

    # Автодополнение первого уровня
    if [[ ${COMP_CWORD} == 1 ]]; then
        #
        # Команда compgen генерирует список возможных вариантов на основе того, что пользователь
        # уже написал. Команда сравнивает ввод с вариантами, которые здесь представлены в виде списка
        # после опции -W и формирует новый список с подходящими вариантами для этого ввода. Аргумент '--'
        # подсказывает compgen, где заканчивается список вариантов.
        # Сгенерированные варианты помещаются в специальный массив COMPREPLY, который оболочка выводит,
        # когда вы нажимаете Tab.
        # Когда ввод пользователя однозначно сопоставляется, то возвращается один вариант.
        #
        COMPREPLY=( $(compgen -W "${options_level1}" -- ${current}) )
        return 0
    fi
    # Когда индекс больше единицы, это значит, что пользователь ввел по меньшей мере один аргумент команды.
    case "${COMP_WORDS[1]}" in
        # опция без продолжения
        work)
            COMPREPLY=()
            return 0
            ;;
        # Опция с конечным числом вариантов
        help)
            COMPREPLY=( $(compgen -W "${options_help}" -- ${current}) )
            return 0
            ;;
        # Опция с большим числом синтаксических ответвлений
        history)
            [[ ${COMP_CWORD} == 2 ]] \
                && COMPREPLY=( $(compgen -W "${options_history}" -- ${current}) ) \
                && return 0
            [[ ${COMP_CWORD} == 3 ]] \
                && COMPREPLY=( $(compgen -W "$(ls ~)" -- ${current}) ) \
                && return 0
            case "${COMP_WORDS[2]}" in
                import)
                    case "${COMP_WORDS[COMP_CWORD-1]}" in
                        --src)
                            COMPREPLY=( $(compgen -d -- ${current}) )   # Генерирует пути к директориям
                            return 0
                            ;;
                        --file)
                            COMPREPLY=( $(compgen -f -- ${current}) )   # Генерирует пути к файлам
                            return 0
                            ;;
                        *)
                            COMPREPLY=( $(compgen -W "${options_import}" -- ${current}) )
                            return 0
                            ;;
                    esac
                    ;;
                export)
                    [[ ${COMP_WORDS[COMP_CWORD-1]} == "-o" ]] \
                        && COMPREPLY=( $(compgen -f -- ${current}) ) \
                        && return 0
                    COMPREPLY=( $(compgen -W "-o" -- ${current}) )
                    return 0
                    ;;
                *)  ;;
            esac
    esac
    return 0
}

# Функция с динамическим правилом должна возвращать 0, когда массив COMPREPLY успешно сформирован.
#
# Привязываем правило.
complete -F _dummy dummy

Теперь если правило привязать к системе автодополнения, то при вводе отдельных частей команды и нажимая Tab ↹, система будет подсказывать, какие варианты ввода сейчас допустимы.

Если вам нужно узнать, есть ли у команды правило автодополнения, то для этого нужно ввести

complete -p dummy



← Код-сниппеты