カテゴリー
【Jqコマンド応用編】xargsをwhile/forループの代わりとして使う場合の勘所
※ 当ページには【広告/PR】を含む場合があります。
2021/05/19
2022/09/30
『処理のパイプ化』
Jqは重いコマンド
1. リファクタリングの観点から、Jqコマンドを含む重めの外部コマンドのループ処理は、
可能な限りパイプライン化したほうがパフォーマンスは向上する。
2. ただし、そもそもjqコマンドをループさせる手法がパフォーマンス的によろしくない。
大幅な高速化を求めるのではあれば、JqではなくAwkやCなどの言語で作成した
コマンドツールなどでスクリプトそのものを見直す。
jqでのループ処理
employee=$(cat << EOF
[
{"社員":"山下モゲ雄","部署":"営業部","事業所":"本社","勤続":"3年"},
{"社員":"島田フガ子","部署":"経理部","事業所":"名古屋支部","勤続":"15年"},
{"社員":"岡田ピポ太","部署":"製造部","事業所":"山口工場","勤続":"8年"},
{"社員":"沢口モフ代","部署":"人事部","事業所":"本社","勤続":"4年"},
{"社員":"銭形ガメ吉","部署":"海外部","事業所":"メキシコ支部","勤続":"11年"},
{"社員":"上岡ムメ美","部署":"営業部","事業所":"本社","勤続":"23年"},
{"社員":"京谷マハ次","部署":"製造部","事業所":"山口工場","勤続":"3年"},
{"社員":"園田フマ由","部署":"人事部","事業所":"本社","勤続":"17年"},
{"社員":"田川ポゥ子","部署":"製造部","事業所":"ベトナム工場","勤続":"12年"},
{"社員":"満田クタ郎","部署":"営業部","事業所":"本社","勤続":"2年"},
{"社員":"島寺ルン大","部署":"営業部","事業所":"本社","勤続":"18年"},
{"社員":"香下ウル蔵","部署":"製造部","事業所":"山口工場","勤続":"5年"},
{"社員":"蒲田ウオ奈","部署":"海外部","事業所":"メキシコ支部","勤続":"9年"},
{"社員":"郷田ポポ生","部署":"営業部","事業所":"名古屋支部","勤続":"4年"},
{"社員":"梅岡ボル伍","部署":"経理部","事業所":"本社","勤続":"25年"},
{"社員":"亀川ヲル士","部署":"製造部","事業所":"山口工場","勤続":"14年"}
]
EOF
)
#!/bin/bash
employee=$(cat << EOF
[
{"社員":"山下モゲ雄","部署":"営業部","事業所":"本社","勤続":"3年"},
#中略...上記で説明したデータセット
{"社員":"山下モゲ雄","部署":"営業部","事業所":"埼玉支部","勤続":"3年"},
]
EOF
)
employee_alt='[]'
for person in $(echo "$employee" | jq -c '.[]'); do
person=$(
echo $person | jq -c --arg division "営業部" --arg branch "埼玉支店" '
(.["部署"] |= $division) | (.["事業所"] |= $branch)
')
employee_alt=$(echo "${employee_alt}" | jq -s -c --argjson person "$person" '.[] + [ $person ]')
done
echo "${employee_alt}" | jq -c '.'
$ hyperfine 'bash 1.sh'
Benchmark #1: bash 1.sh
Time (mean ± σ): 1.084 s ± 0.014 s [User: 1.044 s, System: 0.054 s]
Range (min … max): 1.068 s … 1.117 s 10 runs
xargsで処理パイプ化
$ [CMD1] | xargs -n1 bash -c '[CMD2 $0]'
#!/bin/bash
employee=$(cat << EOF
[
{"社員":"山下モゲ雄","部署":"営業部","事業所":"本社","勤続":"3年"},
#中略...上記で説明したデータセット
{"社員":"山下モゲ雄","部署":"営業部","事業所":"埼玉支部","勤続":"3年"},
]
EOF
)
employee=$(
echo "$employee" | jq -c '.[]' | sed -e 's/"/\\"/g' | xargs -n1 bash -c '
echo "$0" | jq -c "(.[\"部署\"] |= \"営業部\") | (.[\"事業所\"] |= \"埼玉支店\")"
')
echo "["${employee//$'\n'/,}"]" | jq -c '.'
bash -c
"
sed -e 's/"/\\"/g'
$ hyperfine 'bash 2.sh'
Benchmark #1: bash 2.sh
Time (mean ± σ): 589.0 ms ± 9.8 ms [User: 561.7 ms, System: 35.4 ms]
Range (min … max): 578.2 ms … 614.0 ms 10 runs
bash -c '[CMD2 $0]'
export -f
#!/bin/bash
employee=$(cat << EOF
[
{"社員":"山下モゲ雄","部署":"営業部","事業所":"本社","勤続":"3年"},
#中略...上記で説明したデータセット
{"社員":"山下モゲ雄","部署":"営業部","事業所":"埼玉支部","勤続":"3年"},
]
EOF
)
function mod() {
local division='営業部'
local branch='埼玉支店'
echo "$1" | jq -c '. | (.["部署"] |= "'$division'") | (.["事業所"] |= "'$branch'")'
}
export -f mod
employee=$(
echo "$employee" | jq -c '.[]' | sed -e 's/"/\\"/g' | xargs -n1 bash -c '
mod "$0"
')
echo "["${employee//$'\n'/,}"]" | jq -c '.'
$ hyperfine 'bash 3.sh'
Benchmark #1: bash 3.sh
Time (mean ± σ): 591.0 ms ± 7.1 ms [User: 563.1 ms, System: 36.3 ms]
Range (min … max): 579.8 ms … 603.0 ms 10 runs
Jqネイティブのmap関数
#!/bin/bash
employee=$(cat << EOF
[
{"社員":"山下モゲ雄","部署":"営業部","事業所":"本社","勤続":"3年"},
#中略...上記で説明したデータセット
{"社員":"亀川ヲル士","部署":"製造部","事業所":"山口工場","勤続":"14年"}
]
EOF
)
echo "$employee" | jq -c --arg division "営業部" --arg branch "埼玉支店" 'map((.["部署"] |= $division) | (.["事業所"] |= $branch))'
hyperfine 'bash 4.sh'
Benchmark #1: bash 4.sh
Time (mean ± σ): 34.3 ms ± 3.3 ms [User: 33.0 ms, System: 1.8 ms]
Range (min … max): 29.5 ms … 47.5 ms 62 runs
番外編〜Awkでも同等の操作で比較
employee=$(cat <<EOF
山下モゲ雄,営業部,本社,3年
島田フガ子,経理部,名古屋支部,15年
岡田ピポ太,製造部,山口工場,8年
沢口モフ代,人事部,本社,4年
銭形ガメ吉,海外部,メキシコ支部,11年
上岡ムメ美,営業部,本社,23年
京谷マハ次,製造部,山口工場,3年
園田フマ由,人事部,本社,17年
田川ポゥ子,製造部,ベトナム工場,12年
満田クタ郎,営業部,本社,2年
島寺ルン大,営業部,本社,18年
香下ウル蔵,製造部,山口工場,5年
蒲田ウオ奈,海外部,メキシコ支部,9年
郷田ポポ生,営業部,名古屋支部,4年
梅岡ボル伍,経理部,本社,25年
亀川ヲル士,製造部,山口工場,14年
EOF
)
#!/bin/bash
employee=$(cat <<EOF
山下モゲ雄,営業部,本社,3年
#中略...上記で説明したデータ
亀川ヲル士,製造部,山口工場,14年
EOF
)
echo "$employee" | awk -F"," '
BEGIN{OFS=","}
{print $1,"営業部","埼玉支部",$4}
' | jq -c -s -R '
[split("\n")[] | select(length > 0) | split(",")]
| map({"社員":.[0],"部署":.[1],"事業所":.[2],"勤続":.[3]})
'
$ hyperfine 'bash 5.sh'
Benchmark #1: bash 5.sh
Time (mean ± σ): 34.3 ms ± 2.9 ms [User: 33.5 ms, System: 2.0 ms]
Range (min … max): 30.2 ms … 44.7 ms 65 runs
まとめ
1. リファクタリングの観点から、Jqコマンドを含む重めの外部コマンドのループ処理は、
可能な限りパイプライン化したほうがパフォーマンスは向上する。
2. ただし、そもそもjqコマンドをループさせる手法がパフォーマンス的によろしくない。
大幅な高速化を求めるのではあれば、JqではなくAwkやCなどの言語で作成した
コマンドツールなどでスクリプトそのものを見直す。
参考サイト
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー