10 важных советов по написанию Bash-скриптов для максимальной производительности
Ускорьте Bash-скрипты, уменьшив количество форков процессов, используя встроенные команды, пакетные операции с файлами и выбирая правильный инструмент для работы с текстом.
10 важных советов по написанию Bash-скриптов для максимальной производительности
Производительность Bash-скриптов обычно сводится к одному: сколько работы вы заставляете оболочку выполнять по одному процессу за раз. Скрипт, который нормально работает с десятью файлами, может стать мучительно медленным, когда он перебирает пятьдесят тысяч файлов и запускает sed, grep или chmod для каждого элемента.
Используйте эти советы, когда ваш скрипт обрабатывает много файлов, разбирает большой текстовый вывод или выполняется достаточно часто, чтобы небольшие задержки накапливались.
1. Минимизируйте вызов внешних команд
Каждый раз, когда Bash выполняет внешнюю команду (например, grep, awk, sed), он создает новый процесс, что влечет за собой значительные накладные расходы. Самый эффективный способ ускорить скрипт — использовать встроенные возможности Bash, когда это возможно.
Отдавайте предпочтение встроенным командам перед внешними утилитами
Пример: Вместо использования внешних test или [ для условных проверок:
| Медленно (внешняя) | Быстро (встроенная) |
|---|---|
if test -f "$FILE"; then |
if [[ -f "$FILE" ]]; then |
Совет: Для арифметических операций всегда используйте (( ... )) вместо expr или let, так как арифметическое расширение обрабатывается внутри оболочки.
# Медленно
COUNT=$(expr $COUNT + 1)
# Быстро (встроенное арифметическое расширение)
(( COUNT++ ))
2. Используйте эффективные конструкции циклов
Традиционные циклы for, которые перебирают вывод команды, могут быть медленными из-за создания процессов или проблем с разделением слов. Используйте нативное расширение фигурных скобок или циклы while read правильно.
Избегайте for i in $(cat file)
Использование $(cat file) сначала считывает весь файл в память, а затем подвергает его разделению слов, что неэффективно и чревато ошибками, если имена файлов содержат пробелы. Вместо этого используйте цикл while read для построчной обработки:
# Предпочтительный метод для построчной обработки файлов
while IFS= read -r line;
do
echo "Обработка: $line"
done < "data.txt"
Примечание о IFS= read -r: Установка IFS= предотвращает удаление начальных/конечных пробелов, а -r предотвращает интерпретацию обратной косой черты, обеспечивая целостность данных.
3. Обрабатывайте данные внутри с помощью подстановки параметров
Bash предоставляет мощные возможности подстановки параметров (такие как удаление подстроки, замена и преобразование регистра), которые работают внутри со строками, избегая внешних инструментов, таких как sed или awk, для простых задач.
Пример: Удаление префикса
Если вам нужно удалить префикс log_ из переменной filename:
filename="log_report_2023.txt"
# Медленно (внешний sed)
# new_name=$(echo "$filename" | sed 's/^log_//')
# Быстро (встроенное расширение)
new_name=${filename#log_}
echo "$new_name" # Вывод: report_2023.txt
4. Кэшируйте результаты дорогих команд
Если вы выполняете одну и ту же дорогую команду (например, вызов API, сложный поиск файлов) несколько раз в скрипте, кэшируйте результат в переменную или временный файл вместо повторного запуска.
# Запустите это только один раз в начале
GLOBAL_CONFIG=$(get_system_config_from_db)
# Последующие использования читают переменную напрямую
if [[ "$GLOBAL_CONFIG" == *"DEBUG_MODE"* ]]; then
echo "Режим отладки активен."
fi
5. Используйте массивы для списков
При работе со списками элементов используйте массивы Bash вместо строк, разделенных пробелами. Массивы правильно обрабатывают элементы, содержащие пробелы, и в целом более эффективны для итерации и манипуляций.
# Медленный/подверженный ошибкам строковый список
# FILES="file A fileB.txt"
# Быстрый и надежный массив
FILES_ARRAY=( "file A" "fileB.txt" "another file" )
# Эффективная итерация
for f in "${FILES_ARRAY[@]}"; do
process_file "$f"
done
6. Избегайте чрезмерного цитирования и отмены цитирования
Хотя правильное цитирование имеет решающее значение для корректности (особенно при работе с именами файлов, содержащими пробелы), чрезмерное цитирование и отмена цитирования иногда могут добавлять небольшие накладные расходы. Что более важно, понимайте, когда кавычки обязательны, а когда необязательны.
Для арифметического расширения ((...)) кавычки обычно не требуются вокруг самого выражения, в отличие от подстановки команд $().
7. Используйте подстановку процессов для конвейеров, где это возможно
Подстановка процессов (<(cmd)) иногда может создавать более чистые и быстрые конвейеры, чем именованные каналы (mkfifo), особенно когда вам нужно передать вывод одной команды в две разные части другой команды одновременно.
# Эффективное сравнение содержимого двух отсортированных файлов
if cmp <(sort file1.txt) <(sort file2.txt); then
echo "Файлы идентичны после сортировки."
fi
8. Используйте printf вместо echo
Хотя это часто незначительно, поведение echo может различаться в разных оболочках и системах, иногда требуя более сложной обработки для интерпретации обратной косой черты. printf предлагает последовательное форматирование и превосходный контроль, что делает его в целом более надежным и иногда немного более быстрым для операций с большим объемом вывода.
# Последовательный вывод
printf "Пользователь %s вошел в систему в %s\n" "$USER" "$(date +%T)"
9. Отдавайте предпочтение find ... -exec ... {} + перед -exec ... {} ;
При использовании команды find для выполнения другой программы над найденными файлами разница между завершением точкой с запятой (;) и знаком плюс (+) огромна для производительности.
{}; выполняет команду один раз для каждого файла. (Высокие накладные расходы){}+ группирует как можно больше аргументов и выполняет команду один раз (какxargs). (Низкие накладные расходы)
# Медленно: Выполняет 'chmod 644' тысячи раз
find . -name '*.txt' -exec chmod 644 {} \;
# Быстро: Выполняет 'chmod 644' один или несколько раз с множеством аргументов
find . -name '*.txt' -exec chmod 644 {} +
10. Используйте awk или perl для тяжелой обработки текста
Хотя цель состоит в том, чтобы минимизировать внешние вызовы, когда требуется сложная обработка текста, специализированные инструменты, такие как awk или perl, значительно быстрее, чем объединение нескольких команд grep, sed и cut. Эти инструменты обрабатывают данные за один проход.
Если вы обнаружите, что пишете cat file | grep X | sed Y | awk Z, объедините это в один оптимизированный скрипт awk.
Практическое правило
Быстрые Bash-скрипты выполняют меньше работы внутри циклов Bash. Используйте встроенные команды оболочки для простых проверок и редактирования строк, группируйте файловые операции с помощью find -exec ... {} + или xargs и переключайтесь на awk, perl или другой настоящий парсер, когда обработка текста становится основной задачей.
Перед оптимизацией измерьте репрезентативный запуск с помощью time или трассировки оболочки. Затем исправьте циклы, которые порождают больше всего команд.