CUBE SUGAR CONTAINER

技術系のこと書きます。

xargs(1) でシェル関数を使いたい

コマンドラインの処理を並列実行したいときなどに使う xargs(1) だけど、引数にシェル関数を使おうとすると少し工夫する必要がある。 工夫しない場合に失敗する理由から説明しているので、うまくいくやり方だけ知りたいときは下までスクロールしてもらえると。

使った環境は次のとおり。 シェルとしては bash を想定する。 なお、xargs(1) は GNU 版か BSD 版かは問わない。

$ sw_vers       
ProductName:    Mac OS X
ProductVersion: 10.14.6
BuildVersion:   18G5033
$ bash --version 
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)
Copyright (C) 2007 Free Software Foundation, Inc.

とりあえず失敗させてみる

たとえば、次のように greet() という名前でシェル関数を定義しておく。

$ greet() { echo "Hello, $1"; }
$ greet "World"
Hello, World

そして、特に何も考えず上記で定義した関数を xargs(1) 経由で実行しようとすると、次のようにエラーとなる。

$ echo "World" | xargs -I {} greet {}
xargs: greet {}: No such file or directory

コマンドが失敗する理由について

上記でコマンドが失敗する理由は複数ある。 まず、xargs(1) の基本的な動作原理は、プロセスを fork(2) して exec*(2) することに相当する 1 。 この動作原理から、少なくとも xargs(1) の引数は実行可能ファイルが先頭に来る必要があることがわかる。 シェル関数はあくまでシェルの中で利用できるものなので、まずはここが問題となる。

では、次のように bash をインラインで実行すればどうだろうか。 だいぶ確信には迫っているものの、まだ足りないのでエラーになる。

$ echo "World" | xargs -I {} bash -c "greet {}"
bash: greet: command not found

原因の 2 つ目は、xargs(1) 経由で実行したシェルの中でシェル関数が有効ではない点。 そのため、次のように export することでサブプロセスのシェルでもシェル関数が有効であるようにしなければいけない。

$ export -f greet

ようするに、xargs(1) は関係なくインラインで bash を実行したときに、ちゃんとシェル関数が使えるようになっている必要がある。

$ bash -c "greet 'World'"
Hello, World

うまくいくやり方

ということで、うまくいくやり方は次のとおり。

$ greet() { echo "Hello, $1"; }
$ export -f greet
$ echo "World" | xargs -I {} bash -c "greet {}"
Hello, World

いじょう。


  1. 少なくとも GNU 版の実装はそうなっているようだった