xargs
の-P
オプションを使うとパイプで渡された値を任意の並列数で並列処理することができます。以下では、普通に実行すると10秒かかる処理が、5並列で並列処理することによって2秒で実行することができます。
f.sh:
#!/bin/bash echo $*; sleep 1;
$ seq 10 | xargs -I % -P 5 ./f.sh % 1 2 3 4 5 6 7 8 9 10
そのため、シェルスクリプトで大量の項目をfor
を使って処理している部分をxargs
に置き換えることによって高速化することができます。
しかし、xargs
では関数を使用できないので、このままでは並列処理したい部分を都度別のシェルスクリプトに分割しなくてはなりません。
$ function f { echo $*; sleep 1; } $ seq 10 | xargs -I % -P 5 f % xargs: f: No such file or directory
$ echo 'echo $*; sleep 1;' > f.sh $ seq 10 | xargs -I % -P 5 sh ./f.sh % 1 2 3 4 5 6 7 8 9 10
ですが、bash
にはexport -f
という関数をエクスポートできる機能があり、これとbash -c
を組み合わせることによって、いちいち並列処理をしたい部分をシェルスクリプトに書き出さなくてもよくなります。
$ function f { echo $*; sleep 1; } $ export -f f $ seq 10 | xargs -I % -P 5 bash -c 'f %' 1 2 3 4 5 6 7 8 9 10
しかし、xargs
の-I
オプションを使うとデリミタが改行になってしまうので、以下のような場合は意図した動作になりません。
$ function f { echo $*; sleep 1; } $ export -f f $ echo '1 2 3 4 5 6 7 8 9 10' | xargs -I % -P 5 bash -c 'f %' 1 2 3 4 5 6 7 8 9 10
この場合は、xargs
の-d
オプションを使うことでデリミタを指定できるので、上記の場合はスペースを指定すれば意図した動作になります。
$ function f { echo $*; sleep 1; } $ export -f f $ echo '1 2 3 4 5 6 7 8 9 10' | xargs -d ' ' -I % -P 5 bash -c 'f %' 1 2 3 4 5 6 7 8 9 10
しかし、このオプションはGNU版のxargs
にしかないので、Mac(BSD系)だと動きません。なので、brew install findutils
してgxargs
を入れて、alias xargs=gxargs
とする必要があります。またalias
はインタラクティブシェルでないと動作しないので、シェルスクリプトの場合は、さらにshopt -s expand_aliases
とする必要があります。
a.sh:
#!/bin/bash # for Mac(BSD系) shopt -s expand_aliases alias xargs=gxargs function f { echo $*; sleep 1; } export -f f echo '1 2 3 4 5 6 7 8 9 10' | xargs -d ' ' -I % -P 5 bash -c 'f %'
$ ./a.sh 1 2 3 4 5 6 7 8 9 10