我正在尝试按字母顺序对如下所示的 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 排序,该列将位于内部,但它不起作用。
sort -t'>' -k4 file > final.xml
我得到了时髦的输出,它使用排序的内部标签对其他列进行排序。
任何帮助,将不胜感激
答案1
[在慷慨的帮助下善行难陀]
您可以使用xq
来自的包装器来做到这一点yq(jq
YAML/XML 的包装器)利用 的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
将结果转换回 XML,而不是将其保留为 JSON。
请注意, whilesort
在这里代替sort_by(.key)
前者,如果键不唯一,则隐式按值和键排序。
答案2
在每个 Unix 机器上的任何 shell 中使用 any awk
、sort
、 ,cut
并假设您的输入始终采用与您在问题中提供的示例相同的格式,其中要排序的行始终具有开始/结束标记,而其他行没有,并且<
s 没有t 出现在输入中的其他位置:
$ 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>
或者,使用 GNU awk
for 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,awk
您可以使用 anyawk
和 anysort
来实现相同的方法:
$ 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 }
但它会比上面的前两个解决方案慢得多,因为它会生成一个子 shell 来调用sort
每个嵌套行块。
答案3
按要求回答:pure(ish) bash 解决方案(但仍然调用排序)。从示例输入生成指定的输出。当然,它很脆弱,因为任何将 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"