Изучение многозадачности сценариев оболочки Bash с GNU Parallel

Если вам надоело, что ваши сценарии Bash выполняются вечность, этот учебник для вас. Часто вы можете запускать сценарии Bash параллельно, что может значительно ускорить результат. Как? Используя утилиту GNU Parallel, также называемую просто Parallel, с некоторыми удобными примерами GNU Parallel!

Parallel выполняет сценарии Bash параллельно с помощью концепции, называемой многопоточностью. Эта утилита позволяет запускать различные задания на каждом ЦП вместо только одного, сокращая время выполнения сценария.

В этом учебнике вы узнаете о многопоточности сценариев Bash с большим количеством примеров GNU Parallel!

Предварительные требования

Этот учебник будет полон практических демонстраций. Если вы собираетесь следовать за мной, убедитесь, что у вас есть следующее:

  • A Linux computer. Any distribution will work. The tutorial uses Ubuntu 20.04 running on Windows Subsystem for Linux (WSL).
  • Войдите в систему от имени пользователя с привилегиями sudo.

Установка GNU Parallel

Для начала ускорения сценариев Bash с многопоточностью сначала необходимо установить Parallel. Итак, давайте начнем с загрузки и установки.

1. Откройте терминал Bash.

2. Выполните wget для загрузки пакета Parallel. Приведенная ниже команда загружает последнюю версию (parallel-latest) в текущий рабочий каталог.

wget https://ftp.gnu.org/gnu/parallel/parallel-latest.tar.bz2

Если вы предпочитаете использовать более старую версию GNU Parallel, вы можете найти все пакеты на официальном сайте загрузок.

3. Теперь выполните команду tar ниже, чтобы разархивировать только что загруженный пакет.

В данной команде используется флаг x для извлечения архива, j для указания, что архив имеет расширение .bz2, и f для указания файла в качестве входных данных для команды tar. sudo tar -xjf parallel-latest.tar.bz2

sudo tar -xjf parallel-latest.tar.bz2

Теперь у вас должна быть папка с именем parallel- с указанием месяца, дня и года последнего выпуска.

4. Перейдите в папку архива пакета с помощью команды cd. В этом руководстве папка архива пакета называется parallel-20210422, как показано ниже.

Navigating to the Parallel archive folder

5. Затем соберите и установите двоичный файл GNU Parallel, выполнив следующие команды:

./configure
 make
 make install

Теперь проверьте, что Parallel установлен правильно, проверив установленную версию.

parallel --version
Checking the GNU Parallel version

При первом запуске Parallel вы также можете увидеть несколько пугающих строк, содержащих текст вроде perl: warning:. Эти предупреждения указывают, что Parallel не может определить вашу текущую локаль и языковые настройки. Но пока не беспокойтесь о них. Позже вы узнаете, как исправить эти предупреждения.

Настройка GNU Parallel

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

В то время как вы находитесь в вашем терминале Bash, согласитесь на разрешение для академических исследований GNU Parallel, указав параметр citation, за которым следует will cite.

Если вы не хотите поддерживать GNU или его разработчиков, согласие на цитирование не требуется для использования GNU Parallel.

parallel --citation
will cite

Измените локальные настройки, установив следующие переменные окружения, выполнив указанные ниже строки кода. Установка локали и переменных среды языка, подобным образом, не является обязательным требованием. Но GNU Parallel проверяет их при каждом запуске.

Если переменные среды не существуют, Parallel будет жаловаться на них при каждом запуске, как вы видели в предыдущем разделе.

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

export LC_ALL=C man
export LANGUAGE=en_US
export LANG=en_US.UTF-8
Setting locale and language for GNU Parallel

Запуск Ad-Hoc Shell Commands

Давайте начнем использовать GNU Parallel! Сначала вы изучите базовый синтаксис. Как только вы будете уверены в синтаксисе, перейдем к нескольким удобным примерам использования GNU Parallel.

Для начала давайте рассмотрим супер-простой пример, в котором просто выводятся числа от 1 до 5.

1. В вашем терминале Bash выполните следующие команды. Увлекательно, верно? В Bash используется команда echo для отправки чисел от 1 до 5 в терминал. Если бы вы поместили каждую из этих команд в скрипт, Bash выполнил бы их последовательно, ожидая завершения предыдущей.

В этом примере вы выполняете пять команд, которые почти не занимают времени. Но представьте, что если бы эти команды были настоящими скриптами Bash, которые выполняли полезные действия, но требовали вечность для выполнения?

 echo 1
 echo 2
 echo 3
 echo 4
 echo 5

Теперь выполните каждую из этих команд одновременно с помощью Parallel, как показано ниже. В этом примере Parallel запускает команду echo и, обозначенную символом :::, передает этой команде аргументы 1, 2, 3, 4, 5. Три двоеточия сообщают Parallel, что вы предоставляете ввод через командную строку, а не через конвейер (подробнее позже).

В приведенном ниже примере вы передали одну команду Parallel без параметров. Здесь, как и во всех примерах использования Parallel, Parallel запускает новый процесс для каждой команды, используя разные ядра ЦП.

# С командной строки
 parallel echo ::: 1 2 3 4 5

Все команды Parallel следуют синтаксису parallel [Опции] <Команда для многопоточного выполнения>.

3. Чтобы продемонстрировать параллельный прием ввода из конвейера Bash, создайте файл с именем count_file.txt как показано ниже. Каждое число представляет собой аргумент, который вы передадите команде echo.

 1
 2
 3
 4
 5

4. Теперь запустите команду cat, чтобы прочитать этот файл и передать вывод в Parallel, как показано ниже. В этом примере {} представляет каждый аргумент (1-5), который будет передан в Parallel.

# Из конвейера cat count_file.txt | parallel echo {}
GNU Parallel Example #1

Сравнение Bash и GNU Parallel

Сейчас использование Parallel может показаться сложным способом выполнения команд Bash. Но реальное преимущество для вас – это экономия времени. Помните, что Bash будет выполняться только на одном ядере CPU, в то время как GNU Parallel будет работать сразу на нескольких.

1. Чтобы продемонстрировать разницу между последовательными командами Bash и Parallel, создайте сценарий Bash с именем test.sh с следующим кодом. Создайте этот сценарий в том же каталоге, где вы ранее создали count_file.txt.

Сценарий Bash ниже читает файл count_file.txt, засыпает на 1, 2, 3, 4 и 5 секунд, выводит длину сна в терминал и завершается.

#!/bin/bash
 nums=$(cat count_file.txt) # Читаем count_file.txt
 for num in $nums           # Для каждой строки в файле начинаем цикл
 do
     sleep $num             # Читаем строку и ждем столько секунд
     echo $num              # Печатаем строку
 done

2. Теперь запустите сценарий, используя команду time, чтобы измерить, сколько времени займет выполнение сценария. Это займет 15 секунд.

time ./test.sh

3. Теперь используйте команду time снова, чтобы выполнить ту же задачу, но на этот раз используйте Parallel.

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

time cat count_file.txt | parallel "sleep {}; echo {}"
The prompt on the right side of the terminal confirms the time each command took to complete

Знайте Пробный Запуск!

Пришло время перейти к некоторым более реальным примерам использования GNU Parallel. Но прежде, чем вы это сделаете, вы должны знать о флаге --dryrun. Этот флаг пригодится, когда вы хотите увидеть, что произойдет без фактического выполнения Parallel.

Флаг --dryrun может быть финальной проверкой перед запуском команды, которая ведет себя не так, как вы ожидали. К сожалению, если вы введете команду, которая может повредить вашу систему, единственное, что поможет вам GNU Parallel, это сделать это еще быстрее!

parallel --dryrun "rm rf {}"

Пример GNU Parallel №1: Загрузка файлов из Интернета

Для этой задачи вы будете загружать список файлов с различных URL-адресов в Интернете. Например, эти URL-адреса могут представлять веб-страницы, которые вы хотите сохранить, изображения или даже список файлов с FTP-сервера.

Для этого примера вы собираетесь загрузить список архивных пакетов (и файлы SIG) с FTP-сервера GNU Parallel.

1. Создайте файл с именем download_items.txt, возьмите несколько ссылок для загрузки с официального сайта загрузок и добавьте их в файл, разделяя новой строкой.

 https://ftp.gnu.org/gnu/parallel/parallel-20120122.tar.bz2
 https://ftp.gnu.org/gnu/parallel/parallel-20120122.tar.bz2.sig
 https://ftp.gnu.org/gnu/parallel/parallel-20120222.tar.bz2
 https://ftp.gnu.org/gnu/parallel/parallel-20120222.tar.bz2.sig

Вы можете сэкономить время, используя библиотеку Beautiful Soup Python для парсинга всех ссылок с страницы загрузки.

2. Прочтите все URL-адреса из файла download_items.txt и передайте их в Parallel, который вызовет wget и передаст каждый URL-адрес.

cat download_items.txt | parallel wget {}

Не забудьте, что {} в команде parallel является заполнителем для входной строки!

3. Возможно, вам нужно контролировать количество потоков, которые одновременно использует GNU Parallel. Если это так, добавьте параметр --jobs или -j к команде. Параметр --jobs ограничивает количество потоков, которые могут запускаться параллельно, до указанного вами числа.

Например, чтобы ограничить Parallel на загрузку пяти URL-адресов одновременно, команда будет выглядеть следующим образом:

#!/bin/bash
 cat download_items.txt | parallel --jobs 5 wget {}

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

4. Чтобы продемонстрировать эффект параметра --jobs, теперь измените количество заданий и запустите команду time, чтобы измерить, сколько времени занимает каждый запуск.

 time cat download_items.txt | parallel --jobs 5 wget {}
 time cat download_items.txt | parallel --jobs 10 wget {}

Пример GNU Parallel №2: Разархивация архивных пакетов

Теперь, когда у вас есть все эти файлы архивов, скачанные из предыдущего примера, вам нужно их разархивировать.

Находясь в том же каталоге, что и архивные пакеты, запустите следующую команду Parallel. Обратите внимание на использование символа подстановки (*). Поскольку этот каталог содержит как архивные пакеты, так и файлы SIG, вы должны указать Parallel, чтобы он обрабатывал только файлы .tar.bz2.

sudo parallel tar -xjf ::: *.tar.bz2

Бонус! Если вы используете GNU parallel интерактивно (не в сценарии), добавьте флаг --bar, чтобы Parallel показал вам индикатор выполнения задачи в виде полосы прогресса.

Showing the output from the --bar flag

Пример GNU Parallel №3: Удаление файлов

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

Для удаления всех папок, которые начинаются с parallel- с помощью Parallel, перечислите все папки с помощью ls -d и направьте каждый путь к папке в Parallel, вызывая rm -rf для каждой папки, как показано ниже.

Не забудьте флаг --dryrun!

ls -d parallel-*/ | parallel "rm -rf {}"

Заключение

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

Теперь подумайте о всех долго работающих сценариях в вашей среде. Какие из них вы можете ускорить с помощью Parallel?

Source:
https://adamtheautomator.com/how-to-speed-up-bash-scripts-with-multithreading-and-gnu-parallel/