过滤与某些 ID 匹配的 xml 文档

Evg*_*sky 4 grep xml sed awk perl

假设您有一个包含许多 xml 文档的文件,例如

<a>
  <b>
  ...
</a>
in between xml documents there may be plain text log messages
<x>
  ...
</x>

...
Run Code Online (Sandbox Code Playgroud)

我将如何过滤此文件以仅显示给定正则表达式与该 xml 文档的任何一行匹配的那些 xml 文档?我在这里谈论一个简单的文本匹配,所以正则表达式匹配部分可能完全不知道底层格式 - xml。

你可以假设根元素的开始和结束标签总是在它们自己的行上(尽管可能会填充空白),并且它们仅用作根元素,即具有相同名称的标签不会出现在下面根元素。这应该使完成工作成为可能,而不必求助于 xml 感知工具。

iga*_*gal 6

概括

我写了一个 Python 解决方案、一个 Bash 解决方案和一个 Awk 解决方案。所有脚本的想法都是一样的:逐行检查并使用标志变量来跟踪状态(即我们当前是否在 XML 子文档中以及我们是否找到了匹配的行)。

在 Python 脚本中,我将所有行读入一个列表,并跟踪当前 XML 子文档开始的列表索引,以便在到达结束标记时打印出当前子文档。我检查正则表达式模式的每一行,并使用一个标志来跟踪在我们完成处理后是否输出当前子文档。

在 Bash 脚本中,我使用一个临时文件作为缓冲区来存储当前的 XML 子文档,并等到它完成写入后再使用grep检查它是否包含与给定正则表达式匹配的行。

Awk 脚本类似于 Base 脚本,但我使用 Awk 数组作为缓冲区而不是文件。

测试数据文件

data.xml根据您的问题中给出的示例数据,针对以下数据文件 ( )检查了两个脚本:

<a>
  <b>
    string to search for: stuff
  </b>
</a>
in between xml documents there may be plain text log messages
<x>
    unicode string: øæå
</x>
Run Code Online (Sandbox Code Playgroud)

解决方案

这是一个简单的 Python 脚本,可以执行您想要的操作:

<a>
  <b>
    string to search for: stuff
  </b>
</a>
in between xml documents there may be plain text log messages
<x>
    unicode string: øæå
</x>
Run Code Online (Sandbox Code Playgroud)

以下是运行脚本以搜索字符串“stuff”的方法:

#!/usr/bin/env python2
# -*- encoding: ascii -*-
"""xmlgrep.py"""

import sys
import re

invert_match = False

if sys.argv[1] == '-v' or sys.argv[1] == '--invert-match':
    invert_match = True
    sys.argv.pop(0)

regex = sys.argv[1]

# Open the XML-ish file
with open(sys.argv[2], 'r') if len(sys.argv) > 2 else sys.stdin as xmlfile:

    # Read all of the data into a list
    lines = xmlfile.readlines()

    # Use flags to keep track of which XML subdocument we're in
    # and whether or not we've found a match in that document
    start_index = closing_tag = regex_match = False

    # Iterate through all the lines
    for index, line in enumerate(lines):

        # Remove trailing and leading white-space
        line = line.strip()

        # If we have a start_index then we're inside an XML document
        if start_index is not False:

            # If this line is a closing tag then reset the flags
            # and print the document if we found a match
            if line == closing_tag:
                if regex_match != invert_match:
                    print(''.join(lines[start_index:index+1]))
                start_index = closing_tag = regex_match = False

            # If this line is NOT a closing tag then we
            # search the current line for a match
            elif re.search(regex, line):
                regex_match = True

        # If we do NOT have a start_index then we're either at the
        # beginning of a new XML subdocument or we're inbetween
        # XML subdocuments
        else:

            # Check for an opening tag for a new XML subdocument
            match = re.match(r'^<(\w+)>$', line)
            if match:

                # Store the current line number
                start_index = index

                # Construct the matching closing tag
                closing_tag = '</' + match.groups()[0] + '>'
Run Code Online (Sandbox Code Playgroud)

这是输出:

<a>
  <b>
    string to search for: stuff
  </b>
</a>
Run Code Online (Sandbox Code Playgroud)

以下是您如何运行脚本来搜索字符串“øæå”:

python xmlgrep.py stuff data.xml
Run Code Online (Sandbox Code Playgroud)

这是输出:

<x>
    unicode string: øæå
</x>
Run Code Online (Sandbox Code Playgroud)

您还可以指定-v--invert-match搜索不匹配的文档,并在标准输入上工作:

<a>
  <b>
    string to search for: stuff
  </b>
</a>
Run Code Online (Sandbox Code Playgroud)

Bash 解决方案

这是相同基本算法的 bash 实现。它使用标志来跟踪当前行是否属于 XML 文档,并使用临时文件作为缓冲区来存储正在处理的每个 XML 文档。

python xmlgrep.py øæå data.xml
Run Code Online (Sandbox Code Playgroud)

以下是运行脚本以搜索字符串“stuff”的方法:

<x>
    unicode string: øæå
</x>
Run Code Online (Sandbox Code Playgroud)

这是相应的输出:

cat data.xml | python xmlgrep.py -v stuff
Run Code Online (Sandbox Code Playgroud)

以下是您可以如何运行脚本来搜索字符串 'øæå':

bash xmlgrep.sh data.xml 'øæå'
Run Code Online (Sandbox Code Playgroud)

这是相应的输出:

#!/usr/bin/env bash
# xmlgrep.sh

# Get the filename and search pattern from the command-line
FILENAME="$1"
REGEX="$2"

# Use flags to keep track of which XML subdocument we're in
XML_DOC=false
CLOSING_TAG=""

# Use a temporary file to store the current XML subdocument
TEMPFILE="$(mktemp)"

# Reset the internal field separator to preserver white-space
export IFS=''

# Iterate through all the lines of the file
while read LINE; do

    # If we're already in an XML subdocument then update
    # the temporary file and check to see if we've reached
    # the end of the document
    if "${XML_DOC}"; then

        # Append the line to the temp-file
        echo "${LINE}" >> "${TEMPFILE}"

        # If this line is a closing tag then reset the flags
        if echo "${LINE}" | grep -Pq '^\s*'"${CLOSING_TAG}"'\s*$'; then
            XML_DOC=false
            CLOSING_TAG=""

            # Print the document if it contains the match pattern 
            if grep -Pq "${REGEX}" "${TEMPFILE}"; then
                cat "${TEMPFILE}"
            fi
        fi

    # Otherwise we check to see if we've reached
    # the beginning of a new XML subdocument
    elif echo "${LINE}" | grep -Pq '^\s*<\w+>\s*$'; then

        # Extract the tag-name
        TAG_NAME="$(echo "${LINE}" | sed 's/^\s*<\(\w\+\)>\s*$/\1/;tx;d;:x')"

        # Construct the corresponding closing tag
        CLOSING_TAG="</${TAG_NAME}>"

        # Set the XML_DOC flag so we know we're inside an XML subdocument
        XML_DOC=true

        # Start storing the subdocument in the temporary file 
        echo "${LINE}" > "${TEMPFILE}"
    fi
done < "${FILENAME}"
Run Code Online (Sandbox Code Playgroud)

awk 解决方案

这是一个awk解决方案 -awk虽然我的不是很好,所以它很粗糙。它使用与 Bash 和 Python 脚本相同的基本思想。它将每个 XML 文档存储在一个缓冲区(一个awk数组)中,并使用标志来跟踪状态。当它完成处理一个文档时,如果它包含与给定正则表达式匹配的任何行,它就会打印它。这是脚本:

#!/usr/bin/env gawk
# xmlgrep.awk

# Variables:
#
#   XML_DOC
#       XML_DOC=1 if the current line is inside an XML document.
#
#   CLOSING_TAG
#       Stores the closing tag for the current XML document.
#
#   BUFFER_LENGTH
#       Stores the number of lines in the current XML document.
#
#   MATCH
#       MATCH=1 if we found a matching line in the current XML document.
#
#   PATTERN
#       The regular expression pattern to match against (given as a command-line argument).
#

# Initialize Variables
BEGIN{
    XML_DOC=0;
    CLOSING_TAG="";
    BUFFER_LENGTH=0;
    MATCH=0;
}
{
    if (XML_DOC==1) {

        # If we're inside an XML block, add the current line to the buffer
        BUFFER[BUFFER_LENGTH]=$0;
        BUFFER_LENGTH++;

        # If we've reached a closing tag, reset the XML_DOC and CLOSING_TAG flags
        if ($0 ~ CLOSING_TAG) {
            XML_DOC=0;
            CLOSING_TAG="";

            # If there was a match then output the XML document
            if (MATCH==1) {
                for (i in BUFFER) {
                    print BUFFER[i];
                }
            }
        }
        # If we found a matching line then update the MATCH flag
        else {
            if ($0 ~ PATTERN) {
                MATCH=1;
            }
        }
    }
    else {

        # If we reach a new opening tag then start storing the data in the buffer
        if ($0 ~ /<[a-z]+>/) {

            # Set the XML_DOC flag
            XML_DOC=1;

            # Reset the buffer
            delete BUFFER;
            BUFFER[0]=$0;
            BUFFER_LENGTH=1;

            # Reset the match flag
            MATCH=0;

            # Compute the corresponding closing tag
            match($0, /<([a-z]+)>/, match_groups);
            CLOSING_TAG="</" match_groups[1] ">";
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是你如何称呼它的:

bash xmlgrep.sh data.xml 'stuff'
Run Code Online (Sandbox Code Playgroud)

这是相应的输出:

<x>
    unicode string: øæå
</x>
Run Code Online (Sandbox Code Playgroud)