それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

bash の for と パイプ(xargs) の一番大きな違い

bash の似たものシリーズ

初心者が躓きそうなbash で似て紛らわしいものの違いをはっきりさせようとさせるシリーズ。

bash に於ける xargs / for には違いがあるのか?

あります。結構大きな違いが有ります。

それぞれのループの回しかたを復習しておきましょう。

for ループでコマンド結果を回す
for e in some_command ;  echo $e; done 
xargs パイプでコマンド結果を回す
some_command | xargs -I @ echo @; 

この2つの、コマンドの実行には違いが在るのでしょうか。またコマンドの実行の速度には違いが出るでしょうか?

2つの違い

  1. xargsはサブシェルを起動するので変数の名前空間が違う
  2. コマンドの終了を待つfor と 終了を待たない xargs

こんかいは、この後者の違いについて着目したいと思います。

毎秒カウントを出力するスクリプトを作ります。

まず、終了に10秒かかるスクリプトを作ります。

このコマンドは、毎秒1回カウント回数を出力します。

10.times.loop.sh
#!/usr/bin/env bash

for (( i=0; i<10; i++   )) ; do
  echo $i;
  sleep 1 ;
done

単体で実行すると、1秒ずつカウントします。

0    
# sleep 1
1
# sleep 1
2
....
9
xargs で実行したとき。
takuya@~$ bash  10.times.loop.sh | xargs -I@ echo xargs @
xargs 0
xargs 1
xargs 2
xargs 3
xargs 4
xargs 5
xargs 6
xargs 7
xargs 8
xargs 9

for で実行したとき。

takuya@~$ for  i in $( bash 10.times.loop.sh ) ; do   echo for $i;   done ;
for 0
for 1
for 2
for 3
for 4
for 5
for 6
for 7
for 8
for 9

ブログで書くとおなじみに見えます。

ブログで書くと同じに見えます。が、全然違います。

動画で見てください。

xargs の例

youtu.be

for の例

youtu.be

動作の違い

xargs
xargs は 前のコマンドから出力が出るたびに処理される。
for
for だと、前のコマンドが終了するまで何も出来ない。

for とxargs

それは for はコマンドの実行終了を待って処理しているから。 xargs は出力があるたびに stdin から読み込んで逐次処理をするから。

for i in COMMAND; do  echo $i ;done

この場合、for は COMMAND が終了するまでイテレーションの要素数が決まらないので、何も出来ない。

COMMAND | xargs -I @ echo @

この場合、xargs は前のコマンドから受け取って、入力が来たら逐次処理をする。

これは sed などでも同じこと

パイプライン処理の有能さは、非同期に実行できるところにある。

takuya@~$ bash 10.times.loop.sh| sed -e  "s/\([0-9]\)/sed \1/"
sed 0
sed 1
sed 2
sed 3
sed 4
sed 5
sed 6
sed 7
sed 8
sed 9

もしコレを for で回してしまうと・・・コマンドの終了待ちが出てしまう。

for  i in $( bash 10.times.loop.sh )  ; do
      echo $i | sed -e  "s/\([0-9]\)/sed \1/;
 done ;
## for の引数のコマンドの終了待ちが出る

awk でも同じ

takuya@~$ bash 10.times.loop.sh| awk '{ print gensub(/([0-9])/, "awk \\1",1, $0) } '
awk 0 # 逐次処理される。
awk 1
awk 2
awk 3
awk 4
awk 5
awk 6
awk 7
awk 8
awk 9

gensub は gnu awk の機能名ので osxbsd awk にはないけど。

まとめ パイライン

このことは、パイプライン処理では、10.times.loop.sh の標準出力と xargs 標準入力を繋いでから起動することに起因する。

パイプライン処理などで、STDIN/STDOUTを接続するときは、接続される側する側の両方を起動して繋いでコマンド実行しているからだったと思う。詳しくは exec 族 のC言語関数を見ればわかったと思う

時間のかかる処理の結果をループで回すときは、for よりも パイプライン処理をしたほうが無駄が少なくて嬉しい。