jqfpyが--input-format=rawをサポートすればsed,awk的な処理にも対応できるのでは?と思ったので実装してみた。けっこう便利だった。
以前にもちょっとしたjqfpyの活用方法の記事は書いていた。
今回もそれらと似たような話。
--input-format=raw
0.6.0からformatにrawが追加された。とくにJSONやYAMLなどフォーマットを気にせずそのまま文字列として返される(正確には readline().rstrip()
)。
なので、利用者としては単なる改行区切りの文字列と認識してテキトーにコードを書いてあげれば良い。
function gen() { (for i in $(seq 5); do echo $i; done) } $ gen | jqfpy -i raw "1" "2" "3" "4" "5"
クォートされるのが邪魔なら -r, --raw-output
を
$ gen | jqfpy -i raw -r 1 2 3 4 5
1つにまとめたければ --slurp
を
$ gen | jqfpy -i raw -r --slurp -c ["1", "2", "3", "4", "5"]
もちろん、 -u, --unbuffered
にも対応している。少しずつ進行するようなコマンドでも大丈夫。
$ function gen2() { (for i in $(seq 5); do echo $i; sleep 1; done) } $ gen2 | jqfpy -u -i raw 'import time; [time.time(), get()]' -c [1560638479.4626064, "1"] [1560638480.2339017, "2"] [1560638481.2378538, "3"] [1560638482.2408028, "4"] [1560638483.2435904, "5"] # -u なし $ gen2 | jqfpy -i raw 'import time; [time.time(), get()]' -c [1560638535.1345422, "1"] [1560638535.1405966, "2"] [1560638535.1406453, "3"] [1560638535.1406796, "4"] [1560638535.1407096, "5"]
Noneを返すものは無視される
ついでにNoneを返すものは無視するようにした。これは過去にもこのブログで言及していた(jqfpyにloadfile(),dumpfile()を追加していた で dev/nullへのリダイレクトは邪魔だよねという形で)。
$ function gen3() { printf 'foo\nbar\n\nboo'; } $ gen3 | jqfpy -i raw "foo" "bar" "" "boo" $ gen3 | jqfpy -i raw 'get() or None' "foo" "bar" "boo"
無視しないようにするには --show-none
を付ける。
$ gen3 | jqfpy --show-none -i raw 'get() or None' "foo" "bar" null "boo"
nullと表示されている通り、デフォルトではJSONとしての出力される。それを止めたければ -o, --output-format
を付けてあげる。
$ gen3 | jqfpy --show-none -i raw -o raw 'get() or None' 'foo' 'bar' None 'boo'
dictknife の各種コマンドは -i, --input-format
, -o, --output-format
の他にそれらを同時に指定する f, --format
があるのだけれど、こちらにも付けてあげても良いかもしれない。
sedの代わり
例えばsedは以下の様な使いかたをする。oを@に変える。一番シンプルな例かも。
$ echo foo | sed 's/o/@/g' f@@
ふつうのpythonの文字列処理。もちろんpythonでやる分長くはなるけれど。
$ echo foo | jqfpy -i raw 'get().replace("o","@")' -r f@@
正規表現でのマッチなどをやる分には3.8に期待。
awkの代わり
awkはtsvやcsvになりきれていないようなスペース区切りの文字列の一部分だけを取り出すのに使ったりする。個人的にはcutコマンドの上位版というくらいの認識(それ以上詳しくはない)。
$ echo 'foo 20 F' | awk '{printf "%s(%s)さん\n", $1, $2, "\n"}' foo(20)さん
この辺りもまぁsplit()
とかと組み合わせた文字列処理。
$ echo 'foo 20 F' | jqfpy -i raw 'row = get().split(" "); f"{row[0]}({row[1]})さん"' -r foo(20)さん
ちなみにちょっと形を変えてJSONで出力するのも楽といえば楽。zip()
との組み合わせはありと言えばあり。
行区切りの文字列をJSONとして
$ echo 'foo 20 F' | jqfpy -i raw 'row = get().split(" "); props = ["name","age","nickname"]; dict(zip(props, row))' -r { "name": "foo", "age": "20", "nickname": "F" }
スクリプトに持っていくときにpythonの関数に
ちなみに --show-code
を実行してあげれば、ただのpythonの関数としてけっこう手軽に取り出せると思う。オプションが残ったままでも大丈夫(-i raw
とか -r
の話)
$ echo 'foo 20 F' | jqfpy --show-code -i raw 'row = get().split(" "); props = ["name","age","nickname"]; dict(zip(props, row))' -r def _transform(get, h=None): row = get().split(" ") props = ["name","age","nickname"] return dict(zip(props, row))
ちょこっと書き換えればこういう感じに変更するだけなので。
def _transform(line): row = line.split(" ") props = ["name","age","nickname"] return dict(zip(props, row))
おわりに
もう少し複雑な例があれば翻訳してみても良いかも。それなりに冗長にはなるもののpythonがネイティブの人には新しくunix系のコマンドのshell芸的なテクニックを覚えるよりも心理的な負担が少なくなるかも?
ちなみによりawkなどに近づけたものとしてはtseなどがあったりします。