UNIXでは、Bashスクリプトを使用してXMLファイルをソートしますか?

UNIXでは、Bashスクリプトを使用してXMLファイルをソートしますか?

以下に示す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ことができます。awksort

$ 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"

関連情報