以下に示すXMLファイルをアルファベット順に並べ替えたいと思います。これはより大きなbashスクリプトの一部であるため、そのスクリプト内で機能する必要があります。
<Module>
<Settings>
<Dimensions>
<Volume>13000</Volume>
<Width>5000</Width>
<Length>2000</Length>
</Dimensions>
<Stats>
<Mean>1.0</Mean>
<Max>3000</Max>
<Median>250</Median>
</Stats>
</Settings>
<Debug>
<Errors>
<Strike>0</Strike>
<Wag>1</Wag>
<MagicMan>0</MagicMan>
</Errors>
</Debug>
</Module>
私は最終結果が次のようになりたいです。最も内側のタグだけがソートされるようにしたいです。
<Module>
<Settings>
<Dimensions>
<Length>2000</Length>
<Volume>13000</Volume>
<Width>5000</Width>
</Dimensions>
<Stats>
<Max>3000</Max>
<Mean>1.0</Mean>
<Median>250</Median>
</Stats>
</Settings>
<Debug>
<Errors>
<MagicMan>0</MagicMan>
<Strike>0</Strike>
<Wag>1</Wag>
</Errors>
</Debug>
</Module>
-tが>区切り記号で並べ替えてから、内側の4列目に4を並べ替えるなどの並べ替えを使用しようとしましたが、機能しません。
sort -t'>' -k4 file > final.xml
ソートされた内部ラベルを使用して、別の列をソートする奇妙な出力を取得します。
どんな助けでも大変感謝します。
答え1
[惜しみない助けで先行は達成するのが難しい]
xq
次のラッパーを使用してこれを実行できます。yq(YAML / XMLラッパー)はソート機能をjq
利用します。jq
$ xq -x 'getpath([paths(scalars)[0:-1]] | unique | .[])
|= (to_entries|sort_by(.key)|from_entries)' file.xml
<Module>
<Settings>
<Dimensions>
<Length>2000</Length>
<Volume>13000</Volume>
<Width>5000</Width>
</Dimensions>
<Stats>
<Max>3000</Max>
<Mean>1.0</Mean>
<Median>250</Median>
</Stats>
</Settings>
<Debug>
<Errors>
<MagicMan>0</MagicMan>
<Strike>0</Strike>
<Wag>1</Wag>
</Errors>
</Debug>
</Module>
説明する:
paths(scalars)
ルートからリーフまでのすべてのルートのリストを作成し、アレイスライスからリーフ[0,-1]
ノードを削除すると、リーフではなく最も深いノードのルートリストが作成されます。["Module","Settings","Dimensions"] ["Module","Settings","Dimensions"] ["Module","Settings","Dimensions"] ["Module","Settings","Stats"] ["Module","Settings","Stats"] ["Module","Settings","Stats"] ["Module","Debug","Errors"] ["Module","Debug","Errors"] ["Module","Debug","Errors"]
[paths(scalars)[0:-1]] | unique | .[]
として重複した項目を削除できるように、リストを配列に配置しますunique
。イテレータは.[]
それをリストとして返します。["Module","Debug","Errors"] ["Module","Settings","Dimensions"] ["Module","Settings","Stats"]
getpath()
|=
更新割り当て演算子を使用して、内容を並べ替えて更新できる基本オブジェクトに重複排除リストを変換します。
この-x
オプションは、xq
結果をJSONのままにせずに再びXMLに変換するように指示します。
ここでは、前者をsort
置き換えますが、キーが一意でない場合は、値とキーに基づいて暗黙的に並べ替えます。sort_by(.key)
答え2
すべてのUnixシステムのすべてのシェルでany awk
、、、sort
を使用cut
し、入力が常に質問に提供した例と同じ形式であるとします。ここでソートしたい行には常に開始/終了マーカーがあり、他の行にはそうではありません。 、そして<
tを持たないsは、入力の他の場所に表示されます。
$ cat tst.sh
#!/usr/bin/env bash
awk '
BEGIN { FS="<"; OFS="\t" }
{
idx = ( (NF == 3) && (pNF == 3) ? idx : NR )
print idx, $0
pNF = NF
}
' "${@:--}" |
sort -k1,1n -k2,2 |
cut -f2-
$ ./tst.sh file
<Module>
<Settings>
<Dimensions>
<Length>2000</Length>
<Volume>13000</Volume>
<Width>5000</Width>
</Dimensions>
<Stats>
<Max>3000</Max>
<Mean>1.0</Mean>
<Median>250</Median>
</Stats>
</Settings>
<Debug>
<Errors>
<MagicMan>0</MagicMan>
<Strike>0</Strike>
<Wag>1</Wag>
</Errors>
</Debug>
</Module>
上記はawkを使用して入力を装飾するため、sort
ファイルsort
全体に対して一度実行してから追加されcut
た数字を削除するために使用できますawk
。何が起こっているのかを理解できる中間段階は次のとおりです。
awk '
BEGIN { FS="<"; OFS="\t" }
{
idx = ( (NF == 3) && (pNF == 3) ? idx : NR )
print idx, $0
pNF = NF
}
' file
1 <Module>
2 <Settings>
3 <Dimensions>
4 <Volume>13000</Volume>
4 <Width>5000</Width>
4 <Length>2000</Length>
7 </Dimensions>
8 <Stats>
9 <Mean>1.0</Mean>
9 <Max>3000</Max>
9 <Median>250</Median>
12 </Stats>
13 </Settings>
14 <Debug>
15 <Errors>
16 <Strike>0</Strike>
16 <Wag>1</Wag>
16 <MagicMan>0</MagicMan>
19 </Errors>
20 </Debug>
21 </Module>
awk '
BEGIN { FS="<"; OFS="\t" }
{
idx = ( (NF == 3) && (pNF == 3) ? idx : NR )
print idx, $0
pNF = NF
}
' file | sort -k1,1n -k2,2
1 <Module>
2 <Settings>
3 <Dimensions>
4 <Length>2000</Length>
4 <Volume>13000</Volume>
4 <Width>5000</Width>
7 </Dimensions>
8 <Stats>
9 <Max>3000</Max>
9 <Mean>1.0</Mean>
9 <Median>250</Median>
12 </Stats>
13 </Settings>
14 <Debug>
15 <Errors>
16 <MagicMan>0</MagicMan>
16 <Strike>0</Strike>
16 <Wag>1</Wag>
19 </Errors>
20 </Debug>
21 </Module>
awk
または、次の目的でGNUを使用してくださいsorted_in
。
$ cat tst.awk
BEGIN { FS="<" }
NF == 3 {
rows[$0]
f = 1
next
}
f && (NF < 3) {
PROCINFO["sorted_in"] = "@ind_str_asc"
for (row in rows) {
print row
}
delete rows
f = 0
}
{ print }
GNUがない場合は、anyとanyを使用して同じアプローチを得るawk
ことができます。awk
sort
$ cat tst.awk
BEGIN { FS="<" }
NF == 3 {
rows[$0]
f = 1
next
}
f && (NF < 3) {
cmd = "sort"
for (row in rows) {
print row | cmd
}
close(cmd)
delete rows
f = 0
}
{ print }
sort
ただし、入れ子になった各ラインブロックを呼び出すためにサブシェルを作成するので、上記の最初の2つのソリューションよりはるかに遅くなります。
答え3
要求どおりに答えてください:pure(ish)bashソリューション(しかしまだsortを呼び出します)。サンプル入力から指定された出力を生成します。もちろん、XMLを行指向で処理するすべてのソリューションがそうするように、これは脆弱です。
#!/bin/bash
function FunkySort(){
local inputfile="$1"
local -a linestosort=()
local line ltchars
while IFS= read -r line; do
# strip all but less-than characters
ltchars="${line//[^<]}"
# if we guess it is "innermost" tag
if [ ${#ltchars} -gt 1 ]; then
# append to array
linestosort+=("${line}")
else
# if non-innermost but have accumulated some of them
if [ ${#linestosort} -gt 0 ]; then
# then emit accumulated lines in sorted order
printf "%s\n" "${linestosort[@]}" | sort
# and reset array
linestosort=()
fi
printf "%s\n" "$line"
fi
done < "$inputfile"
}
FunkySort "test.xml" >"test.out"