シェルスクリプトで匿名関数(?)を用い標準入力を取らないコマンドをパイプに繋ぐ
恥ずかしいことに全てのコマンドをパイプに繋ぎ込めると思っていたので、関数型言語でよくやるように、x |> f |> g
(xに対し関数fを適用し、その結果に対してさらに関数gを適用する)みたいに繋込もうとしていた。
けれど、コマンドに渡すコマンドライン引数の順序が違ったり、そもそも標準入力を受け付けないコマンドだと安直にパイプで繋げない、ということがわかった。
実際に直面した問題
webpackみたくparcelを使ってjsをバンドルする、ということをやっていたときに、ビルド成果物であるところのjsの名前がハッシュ化(dist/js.e20f4712.js
)されていたためリネームする必要があった。(普段はそのままでいいのだけど。)
よって、ビルド成果物のうち、正規表現\.js$
に合致するものをcontent_scripts.jsにリネーム(コピー)しようとして書いたスクリプトが下記。
$ npx parcel build index.html / # ビルドして成果物一覧を標準出力 | xargs -n1 echo / # 標準出力に1行ずつ出力 | grep \.js$ / # 正規表現\.js$に一致する行を抽出 | cp {ここにパイプから渡ってきた値を配置したい} dist/content_scripts.js
渡ってくるコマンドは第一引数に渡したいのに、第二引数の位置に渡ってくる。こういう場合、関数型言語においてはflipedな(引数の順番をひっくり返した)関数を定義するか、匿名関数を用いることが多い。
そこで調べたところ、シェルスクリプトにも匿名関数っぽく書ける書き方があったので試してみた。
$ npx parcel build index.html / | xargs -n1 echo / | grep /.js$ / | (){ cp $1 dist/content_scripts.js } # 渡ってきた値を変数$1,2...に格納して{}の内部で使えるように。
すると、
usage: cp [-R [-H | -L | -P]] [-fi | -n] [-apvXc] source_file target_file cp [-R [-H | -L | -P]] [-fi | -n] [-apvXc] source_file ... target_directory
なんかsyntaxで怒られてるっぽい。
引数の順番としては間違ってないはずなのに、、、と思って調べていたところ、シェルスクリプトのコマンドは何でもかんでもパイプにつなげるわけではないらしいことがわかった。例えば、echoコマンドとか。
$ echo hoge | echo # -> 何も出力されない
じゃあどうするのか?
1.パイプを使わないパターン
こいつは要するに、x |> f |> g
のように書くのではなく、g(f(x))
のように包含する形の書き方だ。
$ cp $(npx parcel build index.html | xargs -n1 echo | grep \.js$) dist/content_scripts.js
なんだけど、パイプと愚直に引数にとる書き方が混ざっていてデータの流れ、処理の流れが分かりにくい。よって、基本的には次のパイプを使うパターンの方がいいように感じる。
2.別の匿名関数っぽい書き方でパイプを使うパターン
$ npx parcel build index.html \ | xargs -n1 echo \ | grep \.js$ \ | ( v=$(cat); cp $v dist/content_scripts.js ) # 匿名関数っぽい!
スクリプト言語における匿名(ラムダ)関数(\x y -> x + y)
っぽいノリで書けるのがいい。
()じゃなくて{}で書くやり方もあるみたいだけれど、sh/bash/zsh全てで動作確認が取れた()を用いる方がよいのではないかと感じた。
ちなみに、()内で定義した変数はローカル変数になるようだ。({}だとグローバルになる?)詳しくわかってるわけではないのでまた調べてみたい。
※ちなみに、xargsもechoやcpみたく標準入力を取らない(そのままではパイプに繋げない)コマンドに対してパイプで標準入力を引き渡せるように引数を変換してくれているらしい。
以上。