GNU Parallelを使用したMulti-Threading Bashスクリプトの学習

もしBashスクリプトが実行に永遠にかかることにうんざりしているなら、このチュートリアルが役立ちます。よくあることですが、Bashスクリプトを並列で実行することができ、その結果を劇的に高速化することができます。どうやって?GNU Parallelユーティリティを使用します。また、Parallelとも呼ばれるこのユーティリティは、いくつかの便利なGNU Parallelの例と共に、Bashスクリプトを並列で実行します。

Parallelは、マルチスレッディングと呼ばれるコンセプトを介してBashスクリプトを並列で実行します。このユーティリティを使用すると、1つだけではなくCPUごとに異なるジョブを実行できるため、スクリプトの実行時間を短縮できます。

このチュートリアルでは、多くの優れたGNU Parallelの例を使ってマルチスレッディングBashスクリプトを学ぶことができます。

前提条件

このチュートリアルでは実演がたくさんあります。一緒に進めるつもりであれば、以下のものを用意してください:

  • 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を指定して、Parallelにこれを引用することを伝えてください。

GNUやそのメンテナーをサポートしたくない場合は、引用に同意することは必要ありません

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

アドホックなシェルコマンドの実行

GNU Parallelを使い始めましょう!まずは、基本的な構文を学びます。構文に慣れたら、後で便利なGNU Parallelの例を見ていきましょう。

まずは、単純な例として、1から5までの数字を表示することから始めましょう。

1. Bashのターミナルで、以下のコマンドを実行してください。わくわくしませんか?Bashは、echoコマンドを使用して数字の1から5をターミナルに送信します。これらのコマンドをスクリプトに入れて実行すると、Bashはそれぞれのコマンドを順番に実行し、前のコマンドの終了を待ちます。

この例では、ほとんど時間を要しない5つのコマンドを実行しています。しかし、これらのコマンドが実際に役立つ処理を行うが実行に非常に長い時間を要する場合を想像してみてください。

 echo 1
 echo 2
 echo 3
 echo 4
 echo 5

次に、Parallelを使用してそれぞれのコマンドを同時に実行してみましょう。この例では、Parallelは:::で指定されたechoコマンドを実行し、引数12345をそのコマンドに渡します。コロンの数は、Parallelに対してコマンドラインから入力を提供していることを示しています(パイプラインではなく)。

以下の例では、オプションを指定せずにParallelに単一のコマンドを渡しました。ここで、すべてのParallelの例と同様に、Parallelは異なるCPUコアを使用して各コマンドごとに新しいプロセスを開始しました。

# コマンドラインから
 parallel echo ::: 1 2 3 4 5

すべてのParallelコマンドは、parallel [オプション] <マルチスレッドのコマンド>という構文に従います。

3. パイプラインからの入力を並列で受け取ることを示すには、以下のようにcount_file.txtというファイルを作成してください。 各数字は、echoコマンドに渡す引数を表します。

 1
 2
 3
 4
 5

4. 今、catコマンドを実行して、そのファイルを読み取り、出力をParallelに渡します。 この例では、{}はParallelに渡される各引数(1〜5)を表します。

# パイプラインから cat count_file.txt | parallel echo {}
GNU Parallel Example #1

BashとGNU Parallelの比較

現在、Parallelを使用することは、Bashコマンドを実行するやり方として複雑に見えるかもしれません。 しかし、あなたにとって本当の利点は時間の節約です。 Bashは1つの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を使用します。

次のコマンドは同じタスクを実行しますが、最初のループが完了するのを待たずに、各CPUコアで1つずつ実行し、同時に実行できるだけ多くのタスクを開始します。

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フラグを使用します。

--dryrunフラグは、思ったように動作しないコマンドを実行する前の最終的な健全性チェックとして機能します。残念ながら、システムに害を及ぼすコマンドを入力した場合、GNU Parallelが助けることは、それをより速く損なうことだけです!

parallel --dryrun "rm rf {}"

GNU Parallelの例#1:Webからファイルをダウンロードする

このタスクでは、Web上のさまざまなURLからファイルのリストをダウンロードします。たとえば、これらのURLは保存したいWebページ、画像、またはFTPサーバーからのファイルのリストを表すことができます。

この例では、GNU ParallelのFTPサーバーからアーカイブパッケージのリスト(およびSIGファイル)をダウンロードします。

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

PythonのBeautiful Soupライブラリを使用して、ダウンロードページからすべてのリンクをスクレイプすることで、時間を節約できます。

2. download_items.txtファイルからすべてのURLを読み取り、それらをParallelに渡します。Parallelはwgetを呼び出して、それぞれのURLを渡します。

cat download_items.txt | parallel wget {}

並列コマンド内の{}は、入力文字列のプレースホルダーであることを忘れないでください!

3. おそらくGNU Parallelが一度に使用するスレッドの数を制御する必要があります。その場合は、--jobsまたは-jパラメータをコマンドに追加します。 --jobsパラメータは、同時に実行されるスレッドの数を指定した数に制限します。

例えば、Parallelを一度に5つのURLをダウンロードするように制限する場合、コマンドは次のようになります:

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

上記のコマンドの--jobsパラメータは、コンピュータがその数のCPUを持っている限り、任意の数のファイルをダウンロードするために調整することができます。

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を対話的に使用している場合(スクリプトではない場合)、タスクが実行されている間にParallelが進行状況バーを表示するために--barフラグを追加します。

Showing the output from the --bar flag

GNU Parallelの例3:ファイルの削除

例1と例2を実行した場合、作業ディレクトリには多くのフォルダがあり、スペースを占有しています。したがって、これらのファイルを並列ですべて削除しましょう!

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/