awkでjqを呼び出す方法は?

awkでjqを呼び出す方法は?

デフォルトでは、次のようなfile.logがあります。

blah blah
blah blah
Hello world | {"foo": "bar"}
blah blah
Hello earth | {"foo1": "bar1"}

今私の目標は、次のような目的の出力を得るためにいくつかのシェルコマンドを書くことです。

Hello earth | "bar"
Hello earth | "bar1"

現在私が持っているものは次のとおりです。

grep Hello file.log | awk -F "|" '{print $1, system("jq " $2)}'

しかし、jqを呼び出すと、次のエラーが発生します。

jq: error: syntax error, unexpected ':', expecting $end (Unix shell quoting issues?) at <top-level>, line 1:
bin:application   
jq: 1 compile error

私はこれが system() 内で $12 がすべての引用符文字 (") を削除したため、JQ がその json を認識しないからだと思います。

答え1

ここにいくつかの質問があります。

  • system印刷する内容を返すのではなく、実行したコマンドの終了値を返します(すべてがうまくいった場合は0)。 JSONでデコードされたデータの後に1行が表示されます。Hello earth 0
  • JSON文字列の二重引用符はシェルでは無視されます。実行中の結果コマンドは次のとおりですjq {foo: bar}(2つの引数、JSONはもう引用されません)。
  • シェル$2に特殊文字が含まれている場合はそれを解釈します。$
  • 正しい参照があってもjqこれは呼び出されません。最初の引数としてフィルタが必要です(例: " .")、ファイルまたは標準入力からJSON入力を読み取る必要があります。
  • ログでコマンドを作成して実行すると、セキュリティに大きな影響を与えます($2それではどうなりますか; rm -rf ~?)。可能であれば避けるのが最善です。

awkセキュリティ上の問題を除いて、ほとんどの場合動作するコードは次のとおりです。

awk -F "|" '{ printf "%s", $1; system("echo \x27" $2 "\x27 | jq .")}'

それが行うのは、$2単一引用符()を介してstdin\x27に送信することですjq

しかし、問題はまだ存在します

  • 一重引用符が含まれると、コマンド全体$2が中断されます。
  • ダッシュで始めると、$2(可能性なし)オプションとして解釈されます(代わりにコマンドをecho使用できます)。printfecho
  • すでに言及されているセキュリティ問題(たとえば、文字列に$2含まれている場合)...'; rm -r ~; : ' ...

awk今より良いコードがあります

awk -F "|" '{ printf "%s", $1; print $2 | "jq ."; close("jq ."); }'

stdinを介してプロセス$2に転送されますが、今パイプを使用するので、シェルはこれを解釈せずに上記のすべての問題を解決します。コマンドはすべての行で閉じなければなりません(終了)。jqawkjqclose()

答え2

awkを使用せずに他のソリューションを使用するだけです。ジャック

秘訣は次のとおりです。--オリジナル入力、ファイルを文字列配列として読み込みます。

したがって、各行のシンボル|ここで文字列は切り捨てられ、json文字列に解析されます。

jq -j --raw-input  '
    . as $line | 
    if index("|") >= 0  
    then  
      [ .[:index("|")-1] ,.[index("|")+2:] ]  
    else 
      empty
    end | 
   [ .[0] , ( .[1] | fromjson | to_entries | .[0].value ) ] |
   .[0] , " | \"" ,.[1] , "\"\n" '  /tmp/file.log

答え3

xhienneは良い概要を提供します既存のコードの問題と達成しようとしている作業の良い選択肢です。

別のオプションは次のとおりです。呼び出しをまったく試みないで、jqスクリプトが正しいJSON出力を生成するようにawkしてください。awk

$ awk -F '|' 'BEGIN { print "[" } $2 != "" { if (t != "") print t ","; t = $2 } END { print t, "]" }' file | jq .
[
  {
    "foo": "bar"
  },
  {
    "foo1": "bar1"
  }
]

コードawk自体は、見つかったJSONオブジェクトから次のJSON配列を生成します(質問の例を提供します)。

[
 {"foo": "bar"},
 {"foo1": "bar1"} ]

jqこれにより、スクリプトを維持し理解しにくくすることなく、より自由に作業できます。

スクリプトで変数を使用すると、t最後のJSONオブジェクトの後に末尾のコンマは表示されません。

答え4

簡単に言うと:

jq -r -R '
  select(contains(" | ")) |
  split(" | ") |
  .[0] as $text |
  (.[1] | fromjson | to_entries | .[0].value ) as $json_obj_value |
  "\($text) | \($json_obj_value)"
' yourlogfile.log

完全な答え

ほとんどの人はそれがどれほど強力なのか気付いていませんjq(だと言うことはできますがawk)。

〜のようにKusaronandaは彼らの反応で慎重に言及した。、あなたの親友は-R、jsonオブジェクトの代わりにjson文字列で入力を1行ずつ読みます。これにより、内部文字列を自由に処理できjq、まったく必要ありませんawk

ドキュメントでそのバージョンを説明する方法は次のとおりです。1.6:

--raw-input/-R:

入力をJSONに解析しないでください。代わりに、各テキスト行が文字列としてフィルタに渡されます。と一緒に使用すると、入力--slurp全体が1つの長い文字列としてフィルタに渡されます。

希望の出力を得るには、次のものが必要です。-r、端末にjson文字列の代わりにデフォルトの文字列を印刷します。

もう一度ドキュメントから

--raw-output/ -r:

このオプションを使用すると、フィルタ結果が文字列の場合は、引用符付きのJSON文字列形式ではなく標準出力に直接書き込まれます。これは、jqフィルタがJSONベースではなくシステムと通信できるようにするのに役立ちます。

したがって、この問題を解決した後にこの問題を解決する方法はいくつかありますjq

〜のようにEchoMike444はすでにより重要な方法で答えています。、私はもう少し合理化された別のアプローチを試しました。

jq -r -R '
  select(contains(" | ")) |
  split(" | ") |
  .[0] as $text |
  (.[1] | fromjson | to_entries | .[0].value ) as $json_obj_value |
  "\($text) | \($json_obj_value)"
' yourlogfile.log

基本的に私たちは

  1. 「|」を含まない行はすべて削除してください。
  2. 各行を2つの部分に分割
  3. $text読みやすいように左側の部分をステープルで固定します。
  4. 正しい部分をjsonに解析し、最初の値を取得して$json_obj_value読みやすくするためにバインディングに入れます。
  5. 文字列を印刷します"$text | $json_obj_value"\(foo)補間を実行する方法ですjq)。

できるだけコンパクトにしたい場合は、次のものを使用できます。

jq -Rr 'select(contains(" | "))|split(" | ")|"\(.[0]) | \(.[1]|fromjson|to_entries|.[0].value)"' yourlogfile.log

サイズは小さいですが、読み込みも難しいです。どちらが一番良いかは、好みや使い方によって異なります。

関連情報