아래에 표시된 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( 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
그러나 중첩된 각 라인 블록을 호출하기 위해 하위 쉘을 생성하므로 위의 처음 두 솔루션보다 훨씬 느립니다 .
답변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"