Как запускать команды параллельно в Bash

Привет, коллега! Надеюсь, ты выжил после выходных и не сорвал спину на мероприятии по копке картофеля. Сегодня у нас с тобой Bash.

 читать первым в телеграм    читать первым в макс

Мы прекрасно знаем, что bash-скрипты выполняются последовательно. Но есть ли способы запускать команды параллельно?

Конечно, есть, сейчас про это и поговорим. Покажу два способа, как это можно легко осуществить. Первый способ основан на символе &.

Пишем подопытный скрипт:

#!/bin/bash

sleep 5 && echo "sleep 5_1" &
sleep 5 && echo "sleep 5_2" &
wait
echo "sleep 0"

Здесь первая и вторая команды запустятся параллельно. Через 5 секунд на экран выведутся 3 строчки: sleep 5_1, sleep 5_2 и sleep 0.

Оболочка ждёт (wait), пока завершатся два предыдущих процесса, прежде чем запустить следующую команду. Получается, всё то, что в скрипте идёт после wait, будет в режиме ожидания, пока не завершится первый sleep и второй.

Роман Шубин
Роман Шубин
CEO & CTO, Главред в «Цифровой улей»
Задать вопрос
Прикольно, да? Открываются новые возможности для совершенствования своих костылей. Прям терпко попахивает асинхронностью, в хорошем смысле. Про wait поговорим в отдельном посте, есть там свои клёвые фичи.

Теперь давай запустим в терминале последовательно такие команды:

sleep 60 &
sleep 90 &
sleep 120 &

На экран выведутся записи вроде таких:

[1] 38161
[2] 38166
[3] 38167

Это PID процессов, которые ты запустил в фоновом режиме. Но интересует нас тут другое. А именно команда jobs.

Запускаем jobs и смотрим:

[1]   Done    sleep 60
[2]-  Running sleep 90 &
[3]+  Running sleep 120 &

Опа! А это список команд, которые мы запустили в фоне. Первое задание завершилось, остальное ещё шуршит. Некий мониторинг из коробки.

Давай теперь напишем более наглядный пример, где всё это можно применить.

downloader(){
  wget -q "$1"
}

while IFS= read -r url
do
     downloader "$url" &
done < urls.txt

wait
echo "Downloaded complete"
  1. Скрипт читает построчно файл url.txt.
  2. Передаёт каждую строчку в функцию downloader.
  3. Функция downloader каждый раз запускается в фоне.
  4. Происходит скачивание файла по ссылке.
  5. По завершении (wait) получаем компливит complete.

Файл url.txt представляет собой список прямых URL на файлы, которые нужно скачать.

Получается, мы не дожидаемся, пока скачается первый файл, а сразу скачиваем всё пачкой. Мультизагрузка.

Если совсем уж по-простому, то символ & говорит — запусти всё одновременно и параллельно. Типа такого, с таким ты уже всяко встречался:

hostname & date & uname &

Тут всё запустится одновременно, и порядок вывода на экран будет каждый раз свой. Всё зависит от того, с какой скоростью отработает команда.

Роман Шубин
Роман Шубин
CEO & CTO, Главред в «Цифровой улей»
Задать вопрос
Это основной вариант, советую его и использовать, теперь давай рассмотрим альтернативные.

Есть ещё такая утилита parallel, устанавливается, как и всё остальное: apt/yum/brew install parallel.

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

parallel -j 4 wget -q {} < urls.txt

Ключ -j означает, сколько джобов будет запараллелено, что-то типа потоков/threads.

Углубляться сейчас в parallel особого смысла не вижу, но для общего кругозора ты про неё должен знать. Иногда бывает, что для решения задачи подойдёт именно parallel, а не нативка с &.

Ну и на закуску есть такая штука $!, она позволяет узнать PID последнего запущенного процесса. Давай на примере, запускай:

sleep 60 &
[1] 39032
echo $!
39032

В первой команде запустили фоном ожидание 60 секунд, вывелся порядковый номер джобы и PID. Ну а чтобы получить последний PID, выполняем третью команду с $!. На экран вывелся PID в чистом виде, а дальше можешь его сохранить в переменную и уже проверять в скрипте, завершился он или нет.

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