コマンドラインの処理を並列実行したいときなどに使う 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
いじょう。
- 作者:もみじあめ
- 発売日: 2020/02/29
- メディア: Kindle版
-
少なくとも GNU 版の実装はそうなっているようだった↩