FrontPage  Index  Search  Changes  RSS  Login

[system] GNU parallel 使用例

概要

GNU Parallel の Manpage にある Example をメモ。

GNU parallel は xargs の様な感覚で引数を取りながら、処理を並列に実行するコマンド。for や while の代用として、ループを簡潔に書くためにも使える。

例:xargs -n1 の様に引数リストを一つずつ処理する

みつかった HTML ファイルを圧縮する。

$ find . -name '*.html' | parallel gzip

改行やスペースなどを含むファイルへの対処。

$ find . -type f -print0 | parallel -q0 perl -i -pe 's/FOO BAR/FUBAR/g'
  • -q0 オプションを使う

例:コマンドラインから引数リストを読み取る

標準入力の代わりにコマンドラインから引数を受け取る事も出来る。

シェルのグロブにマッチするファイルを圧縮する。

$ parallel gzip ::: *.html

CPU コアにつき 1 プロセスで *.wav を *.mp3 に変換する処理を実行する。

$ parallel -j+0 lame {} -o {.}.mp3 ::: *.wav

例:複数の引数

$ ls | parallel mv {} destdir

-m オプションを使うことで、コマンドラインで許される数まで引数をとることができる。

$ ls | parallel -m mv {} destdir

例:文脈置換(Context replace)

ファイル名の一部を置き換えながら処理したい場合。

$ seq -w 0 9999 | parallel rm pict{}.jpg

以下の様にも実行させられる。

$ seq -w 0 9999 | perl -pe 's/(.*)/pict$1.jpg/' | parallel -m rm

-X オプションを使うことで文脈置換をしつつ可能な限りの数の引数をとれるようになるため、以下の様にも実行させられる。

$ seq -w 0 9999 | parallel -X rm pict{}.jpg

例:集中的な計算と置換

-j+0 オプションで CPU コアの数まで可能な限りのジョブを実行させ、カレントディレクトリ内の画像ファイルのサムネイルを作成する。

$ ls *.jpg | parallel -j +0 convert -geometry 120 {} thumb_{}

find を使い再帰的にファイルを検索する。

$ find . -name '*.jpg' | parallel -j +0 convert -geometry 120 {} {}_thumb.jpg

上記の場合、余計なパスが含まれるので、{.} で拡張子を除いたパスを使う。

$ find . -name '*.jpg' | parallel -j +0 convert -geometry 120 {} {.}_thumb.jpg

例:置換とリダイレクト

  • .gz にマッチするファイルを展開した結果を拡張子 .gz を除いたファイル名にリダイレクトする。
$ parallel zcat {} ">"{.} ::: *.gz
$ parallel "zcat {} >{.}" ::: *.gz

例:組み合わさったコマンド

ジョブはいくつかのコマンドから組み立てることが出来る。

$ ls | parallel 'echo -n {}" "; ls {}|wc -l'
$ ls | parallel '(echo -n {}" "; ls {}|wc -l) > {}.dir'
$ find . | parallel 'a={}; name=${a##*/}; upper=$(echo "$name" | tr "[:lower:]" "[:upper:]"); echo "$name - $upper"'
$ ls | parallel 'mv {} "$(echo {} | tr "[:upper:]" "[:lower:]")"'
$ cat urlfile | parallel "wget {} 2>/dev/null || grep -n {} urlfile"

例:拡張子の除去

{.} を使うとパスから拡張子を除去できる。

$ parallel 'mkdir {.}; cd {.}; unzip ../{}' ::: *.zip
$ parallel -j+0 "zcat {} | bzip2 >{.}.bz2 && rm {}" ::: *.gz
$ find sounddir -type f -name '*.wav' | parallel -j+0 lame {} -o {.}.mp3

{/.} を使うと {/} と {.} の効果を得られる。{/} はパスからディレクトリを取り除く。

$ find sounddir -type f -name '*.wav' | parallel -j+0 lame {} -o mydir/{/.}.mp3

例:2 つの拡張子を取り除き、GNU Parallel 自身から GNU Parallel を呼ぶ

-U を使い拡張子を取り除いた結果を参照するための名前をつけることが出来るため、parallel から呼ぶ parallel の文脈で使いたい {.} との衝突を防ぐことが出来る。

$ ls *.tar.gz| parallel -U {tar} 'echo {tar}|parallel "mkdir -p {.} ; tar -C {.} -xf {.}.tar.gz"'

例:30日ごとに10個の画像をダウンロードする

あるサイトで以下の URL 規則で画像をおいているものとする。

http://www.example.com/path/to/YYYYMMDD_##.jpg
  • YYYYMMDD は日付
  • ## は 01-10 の範囲の数値

以下で、30日前の画像ファイルを wget で取得することが出来る。

$ seq 1 30 | parallel date -d '"today -{} days"' +%Y%m%d \
  | parallel -I {o} seq -w 1 10 "|" parallel wget http://www.example.com/path/to/{o}_{}.jpg

-I は {} に名前をつけるオプション。

例:forループとwhile-readループを書き換える

$ (for x in `cat list` ; do
    do_something $x
  done) | process_output
$ cat list | (while read x ; do
    do_something $x
 done) | process_output

上記のループを以下の様に書くことが出来る。

$ cat list | parallel do_something | process_output
$ (for x in `cat list` ; do
    no_extension=${x%.*};
    do_something $x scale $no_extension.jpg
    do_step2 <$x $no_extension
  done) | process_output
$ cat list | (while read x ; do
    no_extension=${x%.*};
    do_something $x scale $no_extension.jpg
    do_step2 <$x $no_extension
  done) | process_output

上記のループを以下の様に書くことが出来る。

$ cat list | parallel "do_something {} scale {.}.jpg ; do_step2 <{} {.}" | process_output

例:出力をジョブごとにまとめる

GNU parallel は標準でジョブごとに出力をまとめるため、ジョブが終了した後に出力が表示される。

$ parallel traceroute ::: foss.org.my debian.org freenetproject.org

-u オプションで出力のグルーピングを解除できる。

$ parallel -u traceroute ::: foss.org.my debian.org freenetproject.org

例:出力順を入力順に従う

通常、ジョブの出力は完了次第出力する。 -k オプションで入力順の通りに出力させることが出来る。

$ cat textfile | parallel -k echo {} append_string
$ parallel traceroute ::: foss.org.my debian.org freenetproject.org
$ parallel -k traceroute ::: foss.org.my debian.org freenetproject.org

1GB のファイルを 100 個の 10MB のチャンクに分けてダウンロードし、その順序通りに結合した結果をファイルに書き出す。

$ seq 0 99 | parallel -k curl -r \
    {}0000000-{}9999999 http://example.com/the/big/file > file

例:並列 grep

CPU コアあたり 1.5 個のジョブで 1000 個の引数を受け取る grep を実行する。

$ find . -type f | parallel -k -j150% -n 1000 -m grep -H -n STRING {}

例:リモートのコンピュータを使用する

※ リモートコンピュータに SSH を使ってパスワードなしでログイン出来る必要がある。

$ seq 1 10 | parallel --sshlogin server.example.com echo

複数のコンピュータを使用する。

$ seq 1 10 | parallel --sshlogin server.example.com,server2.example.net echo
$ seq 1 10 | parallel --sshlogin server.example.com \
    --sshlogin server2.example.net echo
$ seq 1 10 | parallel --sshlogin server.example.com \
    --sshlogin foo@server2.example.net echo

改行区切りで接続先のホスト名を記録したファイルを指定することも出来る。

$ cat mycomputers
server.example.com
foo@server2.example.com
server3.example.com
$ seq 1 10 | parallel --sshloginfile mycomputers echo

ローカルコンピュータも含めたいのであれば、 ':' を使うことが出来る。

server.example.com
foo@server2.example.com
server3.example.com
:

リモートコンピュータの CPU コア数を指示することが出来る。

$ seq 1 10 | parallel --sshlogin 8/server.example.com echo

例:ファイルの転送

gzip 圧縮してあるローカルのファイルをリモートで展開して bzip2 圧縮し直す。

$ find logs/ -name '*.gz' | \
    parallel --sshlogin server.example.com \
    --transfer "zcat {} | bzip2 -9 >{.}.bz2"
  • ファイルはリモートの $HOME/logs に転送される(リモート側の CWD が $HOME)
    • リモート側パスは $HOME/ 以下のローカル側パスに対応した形となる

--return を使うと結果をローカルに転送し直す。

$ find logs/ -name '*.gz' | \
    parallel --sshlogin server.example.com \
    --transfer --return {.}.bz2 "zcat {} | bzip2 -9 >{.}.bz2"
  • TODO: --return の際の接続方向はリモート→ローカルだろうか?--return がよくわからない。

転送したファイルを削除するためには、--cleanup を使う。リモートコンピュータに転送したファイルと、リモートコンピュータから転送されたファイルの両方を削除する。

$ find logs/ -name '*.gz' | \
    parallel --sshlogin server.example.com \
    --transfer --return {.}.bz2 --cleanup "zcat {} | bzip2 -9 >{.}.bz2"
  • TODO: --return がわからないため、--cleanup もわからない。

複数のリモートコンピュータを使うことが出来る。

$ find logs/ -name '*.gz' | \
    parallel --sshlogin server.example.com,server2.example.com \
    --sshlogin server3.example.com \
    --transfer --return {.}.bz2 --cleanup "zcat {} | bzip2 -9 >{.}.bz2"

ローカルコンピュータを --sshlogin : として指定することが出来る。ローカルのみ、削除と転送を無効化する。

$ find logs/ -name '*.gz' | \
    parallel --sshlogin server.example.com,server2.example.com \
    --sshlogin server3.example.com \
    --sshlogin : \
    --transfer --return {.}.bz2 --cleanup "zcat {} | bzip2 -9 >{.}.bz2"

--transfer, --return, --cleanup の組み合わせを省略して --trc と書ける。

$ find logs/ -name '*.gz' | \
    parallel --sshlogin server.example.com,server2.example.com \
    --sshlogin server3.example.com \
    --sshlogin : \
    --trc {.}.bz2 "zcat {} | bzip2 -9 >{.}.bz2"

リモートコンピュータをファイルに書いた場合。

$ find logs/ -name '*.gz' | parallel --sshloginfile mycomputers \
    --trc {.}.bz2 "zcat {} | bzip2 -9 >{.}.bz2"

~/.parallel/sshloginfile にリモートコンピュータを書いてある場合は -S .. と書ける。

$ find logs/ -name '*.gz' | parallel -S .. \
    --trc {.}.bz2 "zcat {} | bzip2 -9 >{.}.bz2"

例:ローカルとリモートで分散処理

$ parallel --trc {.}.ogg -j+0 -S server2,: \
    'mpg321 -w - {} | oggenc -q0 - -o {.}.ogg' ::: *.mp3

例:一つのコマンドで複数の入力を使う

-N2 で 2 行をひとつの入力として扱う。

$ ls *.es.* | perl -pe 'print; s/\.es//' | parallel -N2 cp {1} {2}

-a で入力ファイルを指定。

$ parallel -a <(seq 6) -a <(seq 6 -1 1) echo

連番ファイルに変換。

$ parallel -a <(find . -type f | sort) -a <(seq 1 $(find . -type f|wc -l)) convert {1} {2}.png
$ find . -type f | sort | parallel convert {} \$PARALLEL_SEQ.png

例:入力として表を使う

以下のファイルがある。

$ cat table_file.tsv
foo<TAB>bar
baz <TAB> quux

これを、以下の様に処理したい。

cmd -o bar -i foo
cmd -o quux -i baz

以下の様にすればよい。

$ parallel -a table_file.tsv --colsep '\t' cmd -o {2} -i {1}

GNU parallel は標準でカラム前後の空白を取り除くため、それを回避するためには以下の様にする。

$ parallel -a table_file.tsv --trim n --colsep '\t' cmd -o {2} -i {1}

例:cat | sh のように

ping など、小さなリソースを扱うジョブは CPU, ディスク I/O, ネットワーク I/O の使用が少ないものを考える。

$ cat jobs_to_run
ping -c 1 10.0.0.1
wget http://status-server/status.cgi?ip=10.0.0.1
ping -c 1 10.0.0.2
wget http://status-server/status.cgi?ip=10.0.0.2
...
ping -c 1 10.0.0.255
wget http://status-server/status.cgi?ip=10.0.0.255

上記のファイルを入力として 100 プロセスで並列に処理させる。

例:ミューテックスとカウンティングセマフォ

sem コマンドは parallel --semaphore のエイリアス。

カウンティングセマフォは、バックグラウンドで実行するジョブを指定数までに制限する。バックグラウンドで実行している序部数が指定数に達している場合は、次のジョブを待たせる。また、sem --wait ですべてのジョブが完了するのを待つ。

$ for i in `ls *.log` ; do
    echo $i
    sem -j10 gzip $i ";" echo done
  done
$ sem --wait

ミューテックスはジョブの実行を一つに制限したカウンティングセマフォ。

$ seq 1 3 | parallel sem sed -i -e 'i{}' myfile

セマフォに名前をつけることで同時に複数の異なるセマフォを利用できる。

$ seq 1 3 | parallel sem --id mymutex sed -i -e 'i{}' myfile

例:標準入力からファイル名付きでエディタを起動する

$ cat filelist | parallel -T -X emacs
$ cat filelist | parallel -T -X vi

例:キューシステム、バッチマネージャとして利用する

GNU parallel は継続的に入力ファイルを読み込む。

$ echo >jobqueue; tail -f jobqueue | parallel

ジョブを追加する。

$ echo my_command my_arg >> jobqueue

リモートでも実行させられる。

$ echo >jobqueue; tail -f jobqueue | parallel -S ..

キューシステムもしくはバッチマネージャとして使うには 2 つの要件がある。

  • You will get a warning if you do not submit JobSlots jobs within the first second. E.g. if you have 8 cores and use -j+2 you have to submit 10 jobs. These can be dummy jobs (e.g. echo foo). You can also simply ignore the warning.
  • Jobs will be run immediately, but output from jobs will only be printed when JobSlots more jobs has been started. E.g. if you have 10 jobslots then the output from the first completed job will only be printed when job 11 is started.

例:ディレクトリプロセッサとして使う

my_dir 以下のファイル書き込みを監視して parallel への入力とする。

$ inotifywait -q -m -r -e CLOSE_WRITE --format %w%f my_dir | parallel -u echo
$ inotifywait -q -m -r -e CLOSE_WRITE --format %w%f my_dir | parallel -S ..  -u echo
Last modified:2011/02/05 22:38:38
Keyword(s):[system]
References: