かなり複雑な2つの式がありますが、jq
1つが別の1つの補数を返すことだけが異なります。つまり、2つの間の唯一の違いは、1つがselect(expression)
別のものが返すものを返すことですselect(expression|not)
。
単純化された例:
$ jq -n '$ARGS.positional[] | select( . > 2 )' --jsonargs 1 2 3 4 5
3
4
5
$ jq -n '$ARGS.positional[] | select( . > 2 | not )' --jsonargs 1 2 3 4 5
1
2
私のコードでこれら2つの異なる式を繰り返すのではなくjq
(各式は実際には数行の長さです)、単一の式に値を渡すことで2つの動作を切り替えることができます。これは非常に簡潔です。
どうすればいいですか?
実際のjq
コード(メッセージペイロードでエンコードされたファイルパスに基づいてRabbitMQメッセージをフィルタリング):
map(
# Add an array of pathnames that would match this message. This
# includes the pathnames of each parent directory, leading up to
# and including the pathname of the file itself.
.tmp_paths = [
# The full pathname is part of a base64-encodod JSON blob.
foreach (
.payload |
@base64d |
fromjson.filepath |
split("/")[]
) as $elem (
null;
. += $elem + "/";
.
)
] |
# The last element is the full file path and should not have a
# trailing slash.
.tmp_paths[-1] |= rtrimstr("/")
) |
[
# Match the pathnames given as positional command line arguments
# against the computed pathnames in the "tmp_paths" array in
# each message. Extract the messages with a match.
JOIN(
INDEX($ARGS.positional[]; .);
.[];
.tmp_paths[];
if (.[1:] | any) then
.[0]
else
empty
end
)
] |
# Deduplicate the extracted messages on the full pathname of the file.
# Then remove the "tmp_paths" array from each message and base64 encode
# them.
unique_by(.tmp_paths[-1])[] |
del(.tmp_paths) |
@base64
if
私は、ファイルパスが場所引数として指定されたパス名と一致するメッセージを抽出または削除するように、任意の方法でステートメントを変更する必要があると仮定します。
答え1
ブール値をjq
式に渡し、if
- ステートメントを使用して選択したセットまたは対応する補数の戻り値を切り替えます。
$ jq -n --argjson yes true '$ARGS.positional[] | select( . > 2 | if $yes then . else not end )' --jsonargs 1 2 3 4 5
3
4
5
$ jq -n --argjson yes false '$ARGS.positional[] | select( . > 2 | if $yes then . else not end )' --jsonargs 1 2 3 4 5
1
2
より複雑なjq
式はif
ステートメントを変更します。
# Match the pathnames given as positional command line arguments
# against the computed pathnames in the "tmp_paths" array in
# each message. Depending on the $yes boolean variable, extract
# or discard matching messages.
JOIN(
INDEX($ARGS.positional[]; .);
.[];
.tmp_paths[];
if (.[1:] | any | if $yes then . else not end) then
.[0]
else
empty
end
)
if $yes then . else not end
これは、変数が$yes
集合を望むのか、それともその補数を望むのかについての「トグル」として機能できることに注意してください。単純化されたselect()
形式とより複雑な形式の両方で、JOIN()
このif
ステートメントはブールテスト結果に対して機能し、要素が結果セットの一部かどうかを判断します。
答え2
これ@Kusalanandaが説明するソリューションシンプルで読みやすく、コンパクトで非常に高速なので、すべての一般的な状況、特に時々発生するケースを処理するための最良の方法です。
安定した設定を確実にするためにこの切り替え動作を頻繁に使用したり、速度を上げるためにさらに努力したい場合は、他のアプローチを検討してください。
実際、この素晴らしいシンプルなアプローチには、if ... then ... else ... end
ストリーム内の各オブジェクトに対して追加の比較を追加する必要があるという欠点があります。この比較は、結果が常にコマンドラインの静的入力として事前に知られており、実行中に変更されないため、少し無駄になります。
これらの比較を排除する1つの可能な方法は、モジュールで定義されている関数を使用してから、コマンドラインからその関数を選択することです。
考慮する:
# let's set the thing up
$ mkdir dot && echo 'def dot_or_not: .;' > dot/.jq
$ mkdir not && echo 'def dot_or_not: not;' > not/.jq
# now let's use it
$ seq 5 | jq 'include "./"; select ( . > 2 | dot_or_not )' -Ldot
3
4
5
$ seq 5 | jq 'include "./"; select ( . > 2 | dot_or_not )' -Lnot
1
2
シングルプロセッサ仮想マシンのいくつかの簡単なベンチマークでは、このアプローチは基本的なアプローチよりも平均5倍高速ですが、実証された大規模な計算if ... then ... else ... end
の「経済性」には影響しません。
私自身もおそらくそれほど簡単な作業のためにここまで行かないでしょう...他の可能な(より価値のある)モジュールの上にそれぞれの追加の「スイッチ」がますます面倒で扱いにくくなるからです。実際、私は本当にさまざまな計算バリアントのためにモジュールを使用したいのですが...でもそうです。
完全性を達成するために、スペクトルの反対側の端では、別のアプローチが次のように見える。
$ seq 5 | jq 'select( . > 2 | [not,.][$yes] )' --argjson yes 1
3
4
5
$ seq 5 | jq 'select( . > 2 | [not,.][$yes] )' --argjson yes 0
1
2
またはそのいとこの変形:
$ seq 5 | jq 'select( . > 2 | {(tostring):1}[$yes] )' --arg yes true
3
4
5
$ seq 5 | jq 'select( . > 2 | {(tostring):1}[$yes] )' --arg yes false
1
2
if ... then ... else ... end
これらの方法はコンパクトに見えますが、残念ながら、それぞれ2つの揮発性オブジェクトの構成とルックアップを追加するため、基本的な方法よりはるかに遅いです(簡単なベンチマークでは2〜4倍遅い)。