Все статьи / Советы по написанию Bash-скриптов

Серия максимально полезных советов по написанию простых сценариев на Bash


Исполнение скриптов в Linux

В начале каждого скрипта должен стоять shebang-комментарий, указывающий, с каким интерпретатором нужно выполнять скрипт:

#!/usr/bin/env bash
# Скрипт выполняется в интерпретаторе Bash, т.е. это скрипт на языке Shell
#!/usr/bin/python
# Скрипт выполняется в интерпретаторе Python, т.е. это скрипт на языке Python

В разных языках используются разные символы начала комментария: в Bash и Python однострочные комментарии начинаются с “#”, в других языках может быть иной стиль. Однако, все языки с поддержкой интерпретации в Linux-консоли позволяют написать shebang-комментарий в начале файла. Обработкой shebang-комментария занимается системная оболочка.

Для запуска скрипта напрямую из консоли (терминала) на файл скрипта нужно установить права на запуск. Это можно сделать, например, командой chmod, либо в свойствах файла в файловом менеджере:

# Добавляет права на исполнение любым пользователем файлу reformat.sh
chmod +x reformat.sh 

Основы работы bash

Чтобы присвоить переменную, используйте оператор “=” без пробелов вокруг него:

url=http://stackoverflow.com/questions/5998066

# wget скачает и сохранит контент, который сервер отдаёт для указанного url
wget -O page.html $url

Чтобы выполнить команду и сохранить результат в переменую, используйте данную конструкцию без пробелов вокруг “=”:

files=$(ls -la)

Текущий рабочий каталог и каталог скрипта

Есть огромная разница между текущим каталогом (англ. working directory или current directory) и каталогом, где находится скрипт:

  • при запуске каждый процесс в системе, включая интерпретатор скрипта, получает некоторый каталог в качестве working directory
    • процесс может изменить свой working directory, но обычно этого не происходит, т.к. нет причин его менять
  • все относительные пути превращаются в абсолютные путём добавления пути к working directory в начало
  • путь к текущему каталогу в Bash хранится в переменной $PWD
  • сменить текущий каталог можно командой cd путь-к-каталогу

Примеры получения working directory и каталога скрипта:

# Выводим в консоль путь к working directory запущенного скрипта
echo $PWD

# Сохраняем в переменную BASEDIR путь к каталогу, где находится скрипт
BASEDIR=$(dirname $(realpath "$0"))
# Выводим в консоль значение BASEDIR
echo $BASEDIR

Чаще всего скрипт запускается из каталога, в котором лежит файл скрипта, и тогда, конечно же, working directory содержит в себе этот скрипт. Это вызывает ошибки со стороны начинающих программистов: новичок думает, что для вызова другого скрипта достаточно указать его имя, хотя на самом деле нужно было получить абсолютный путь и указывать его, иначе при выполнении первичного скрипта из другого каталога второй скрипт не будет найден.

Условное выполнение

Допустим, есть две строковые переменные foo и bar. Требуется выполнить команду <команда>, если переменные равны. Сделать это можно следующими двумя способами:

if [ "$foo" = "$bar" ]; then <команда>; fi
if [[ "$foo" == "$bar" ]]; then <команда>; fi

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

Обход каталогов и файлов

Для перечисления файлов с заданным расширением в текущем каталоге подходит обычная инструкция for .. in .. do / done

  • Если ни одного файла с заданным расширением нет, вместо пути к файлу вы получите искомую маску, т.е. в примере ниже в этом случае будет перебор строк "*.cpp", "*.sh", "*.md"
# Переменная filepath будет содержать относительный путь
for filepath in *.cpp *.sh *.md; do
    # обращение к переменной i
    echo $filepath
    # на случай пробелов в пути файла или конкатенации
    echo "${filepath}"
done

Для рекурсивного перечисления файлов в заданном каталоге и его подкаталогах подойдёт find:

# Возвращает список всех относительных путей файлов в текущем каталоге
find . -type f
# Возвращает список всех относительных путей файлов
#  с расширениями *.cpp в текущем каталоге
find . -type f -name "*.cpp"

# Возвращает список всех относительных путей файлов
#  с расширениями *.cpp или *.h в текущем каталоге
find . -type f \( -name "*.cpp" -or -name "*.h" \)

# Сохраняем в переменную все относительные пути
#  с расширениями *.cpp или *.h в текущем каталоге
filepaths=$(find . -type f \( -name "*.cpp" -or -name "*.h" \))
# Обрабатываем полученные пути в цикле
for filepath in $filepaths; do
    echo "Processing ${filepath}..."
done

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

Интерактивные консольные программы, такие как nano, less или man, могут быть приостановлены комбинацией клавиш Ctrl+Z. Приостановка приводит к возврату в интерпретатор Bash и в консоль пишется подобный текст:

[1]+  Остановлено  nano expr_1.ll

Восстановить приостановленную программу можно с помощью команды fg без аргументов. При этом программа востановится, а на заднем плане в консоль будет написано следующее:

nano expr_1.ll
Используйте «fg» для возврата в nano