如何根据“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
这不一定是正确的工具,因为它适用于空格分隔或固定宽度的文件(从其仅有的两个“列”相关选项是--skip-fields
和可见--skip-chars
),而您这里拥有的是类似 XML 的列宽既不固定也不存在任何简单的单字符分隔符的数据(groupName
原则上等的值可以包含空格)。
相反,我会使用用于处理 XML 的工具。
避免自己编写脚本的一种选择是基于 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"/>
相比之下,缺点uniq
是basex
首先将整个 XML 文档读入内存,因此对于超过主内存大小的非常大的文件,这是不可行的。有一些 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>
(我刚刚添加了一个名为 的根节点Groups
),那么您可以使用xq
XML 解析器包装器jq
,来自https://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>
作为参考,由于 是xq
建立在 之上的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) 每行的属性顺序完全相同c) 之前的属性中永远不会有任何带有空格的行groupName
(也不会在groupName
自己的值内):
这个答案展示了如何awk
根据空格分隔的字段过滤掉重复项。在您的情况下,这也将是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"/>