XMLファイルの特定の属性名に基づいて重複タグを削除する方法は?

XMLファイルの特定の属性名に基づいて重複タグを削除する方法は?

「groupName」に基づいて重複行を削除して行を保持する方法はdirectoryId="1"

<Group id="123" groupName="ABC" lowerGroupName="abc" active="1" local="1" createdDate="2017-08-21 09:28:30.581" updatedDate="2017-08-21 09:28:30.581" type="GROUP" directoryId="10100"/>
<Group id="456" groupName="ABC" lowerGroupName="abc" active="1" local="0" createdDate="2017-08-21 09:28:30.634" updatedDate="2017-08-21 09:28:30.634" type="GROUP" directoryId="1"/>

答え1

私はuniqこれがスペースで区切られたファイルや固定幅ファイル(たった2つの「列」関連オプションがあり、表示されるファイル--skip-fields--skip-charsで動作するため、必ずしも正しいツールだとは思いません。一方、ここにあるのはXMLと同様の列です。幅データには固定区切り記号や単純な単一文字区切り記号はありません(groupName原則として、等しい値にはスペースを含めることができます)。

代わりにXML処理ツールを使用します。

独自のスクリプトを作成しない1つのオプションはXPathベースのフィルタリングです。次の回答では、XPathを使用して一意性をフィルタリングする方法を学ぶことができます。これら- 重要な文法要素はfollowing-sibling::合軸ですpreceding-sibling::。 XPath式を評価するためのコマンドラインツールは、次の質問に対する回答にあります。この問題。私が試したことのうち、インストールが最も簡単なのはbasex(推奨)ここ)だから、以下で使用します。

あなたの質問を正しく理解したら、同じ行(XML要素)を持つ行を最後の行に減らしたいと思いますgroupName(または行がある行を選択する他の理由はありますかdirectoryId="1"?)。次のXML文書の場合:

<Groups>
<Group id="123" groupName="ABC" lowerGroupName="abc" active="1" local="1" createdDate="2017-08-21 09:28:30.581" updatedDate="2017-08-21 09:28:30.581" type="GROUP" directoryId="10100"/>
<Group id="456" groupName="ABC" lowerGroupName="abc" active="1" local="0" createdDate="2017-08-21 09:28:30.634" updatedDate="2017-08-21 09:28:30.634" type="GROUP" directoryId="1"/>
<Groups>

Groupsこれを達成するには、ルート要素()のすべての項目をラップする必要があります。正しい形式のXML、この要件は、次のXPath式を介して達成できます。

/Groups/Group[not(@groupName = following-sibling::Group/@groupName)]

/Groups/Group返す要素を選択し、の式を使用してフィルタリングします[]@プロパティを選択し、following-sibling::現在のプロパティのすべての後続の兄弟エントリと一致します(参照:ここ)。

これを実行すると、basex予想される結果が生成されます。

$ basex -i - '/Groups/Group[not(@groupName = following-sibling::Group/@groupName)]'

# [paste this into the terminal:]

<Groups>
<Group id="123" groupName="ABC" lowerGroupName="abc" active="1" local="1" createdDate="2017-08-21 09:28:30.581" updatedDate="2017-08-21 09:28:30.581" type="GROUP" directoryId="10100"/>
<Group id="456" groupName="ABC" lowerGroupName="abc" active="1" local="0" createdDate="2017-08-21 09:28:30.634" updatedDate="2017-08-21 09:28:30.634" type="GROUP" directoryId="1"/>
</Groups>

# [output:]

<Group id="456" groupName="ABC" lowerGroupName="abc" active="1" local="0" createdDate="2017-08-21 09:28:30.634" updatedDate="2017-08-21 09:28:30.634" type="GROUP" directoryId="1"/>

これとは対照的に、欠点は、uniqXMLbasex文書全体が最初にメモリに読み込まれるため、メインメモリサイズを超える非常に大きなファイルには適していないことです。いくつかのXMLプロセッサがあります。ストリーミング方式でのXML操作たとえば、XSLT 3.0にストリーミング変換があるため、大容量ファイルを処理する必要がある場合は、XSLT 3.0をサポートするプロセッサを使用して処理する方法があります。しかし、それまでは、独自の小さなストリームパーサーを手動で作成する方が簡単かもしれません。

答え2

XML文書がうまく構成されているとします。

<Groups>
<Group id="123" groupName="ABC" lowerGroupName="abc" active="1" local="1" createdDate="2017-08-21 09:28:30.581" updatedDate="2017-08-21 09:28:30.581" type="GROUP" directoryId="10100"/>
<Group id="456" groupName="ABC" lowerGroupName="abc" active="1" local="0" createdDate="2017-08-21 09:28:30.634" updatedDate="2017-08-21 09:28:30.634" type="GROUP" directoryId="1"/>
</Groups>

(私はルートノードを追加しました。)次のXMLパーサーラッパーをGroups使用できます。xqjqhttps://kislyuk.github.io/yq/、このように:

xq -x '.[].Group |= unique_by(."@groupName")' file.xml

Groupこれは属性に基づいて一意のノードのみを保持しますgroupName。表示された最初の属性値ノードが保持されます。

上記のコマンドを最上位のXMLに適用した結果は次のとおりです。

<Groups>
  <Group id="123" groupName="ABC" lowerGroupName="abc" active="1" local="1" createdDate="2017-08-21 09:28:30.581" updatedDate="2017-08-21 09:28:30.581" type="GROUP" directoryId="10100"></Group>
</Groups>

最も低い属性値を持つノードを取得するには、directoryIDリストを一意にする前に、その値でノードをソートします。

xq -x '.[].Group |= (sort_by(."@directoryId") | unique_by(."@groupName"))' file.xml

これにより

<Groups>
  <Group id="456" groupName="ABC" lowerGroupName="abc" active="1" local="0" createdDate="2017-08-21 09:28:30.634" updatedDate="2017-08-21 09:28:30.634" type="GROUP" directoryId="1"></Group>
</Groups>

ちなみに、この式はxqtopに基づいて書かれているので、jq実際にはXML文書を翻訳したJSON文書に適用されます。変更されたJSON文書は再びXMLに変換されます。この回答の上部にあるXMLを考慮すると、変更されたJSON文書は次のようになります。

{
  "Groups": {
    "Group": [
      {
        "@id": "123",
        "@groupName": "ABC",
        "@lowerGroupName": "abc",
        "@active": "1",
        "@local": "1",
        "@createdDate": "2017-08-21 09:28:30.581",
        "@updatedDate": "2017-08-21 09:28:30.581",
        "@type": "GROUP",
        "@directoryId": "10100"
      },
      {
        "@id": "456",
        "@groupName": "ABC",
        "@lowerGroupName": "abc",
        "@active": "1",
        "@local": "0",
        "@createdDate": "2017-08-21 09:28:30.634",
        "@updatedDate": "2017-08-21 09:28:30.634",
        "@type": "GROUP",
        "@directoryId": "1"
      }
    ]
  }
}

答え3

識別子を使用して行を識別します。grep 'groupName="ABC"'

除外基準を選択解除する特定の行:grep -v 'directoryId="1"'

削除したい行が表示されます。これで、重複行を強制的に適用して具体的に削除できます。

grep 'groupName="ABC"' input-file | grep -v 'directoryId="1"' > to-remove
cat input-file to-remove | sort | uniq -u > output-file

最後にすべてをクリーンアップするには、次を追加できます。

rm to-remove input-file
mv output-file input-file

警告するこれにより、入力ファイルの内容が並べ替えられます。アイテムのリストだけがあり、他の構造はない場合は、この解決策で十分です。

答え4

他の答えはデータのXML特性を無視しますが、a)これは生産ワークフローではなく「ワンタイム」として使用されるという仮定の下でのみ有効です。 b) 各行の属性はまったく同じであるgroupNamegroupName

この回答awkスペースで区切られたフィールドに基づいて重複項目をフィルタリングする方法を示します。あなたの場合、セクションはスペースで区切られた3番目の列であるため、これはawk '!seen[$3]++'同じです。groupNameしかし、私が正しく理解したなら、あなたは最後awk最初の行ではなく、各「繰り返しグループ」の行です(上記の行が提供するものです)。これを達成するには、tacに入力する前に行の順序を単に反転し、再び反転してawk元の順序を復元します。

$ tac | awk '!seen[$3]++' | tac

# [paste this into the terminal:]

<Group id="123" groupName="ABC" lowerGroupName="abc" active="1" local="1" createdDate="2017-08-21 09:28:30.581" updatedDate="2017-08-21 09:28:30.581" type="GROUP" directoryId="10100"/>
<Group id="456" groupName="ABC" lowerGroupName="abc" active="1" local="0" createdDate="2017-08-21 09:28:30.634" updatedDate="2017-08-21 09:28:30.634" type="GROUP" directoryId="1"/>

# [output is:]

<Group id="456" groupName="ABC" lowerGroupName="abc" active="1" local="0" createdDate="2017-08-21 09:28:30.634" updatedDate="2017-08-21 09:28:30.634" type="GROUP" directoryId="1"/>

関連情報