Как сохранить и упорядочить вывод команды в сценарии awk?

Резюме

Я написал решение для Python, решение для Bash и решение для Awk. Идея для всех скриптов одна и та же :пройти через строку -за строкой -и использовать переменные флага для отслеживания состояния (, т.е. нет, мы нашли совпадающую строку ).

В скрипте 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>

Решение Python

Вот простой скрипт Python, который делает то, что вы хотите:

#!/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] + '>'

Вот как запустить скрипт для поиска строки "stuff":

python xmlgrep.py stuff data.xml

А вот и результат:

<a>
  <b>
    string to search for: stuff
  </b>
</a>

А вот как запустить скрипт для поиска строки "øæå":

python xmlgrep.py øæå data.xml

А вот и результат:

<x>
    unicode string: øæå
</x>

Вы также можете указать -vили --invert-matchдля поиска не соответствующих -документов и работать со стандартным вводом:

cat data.xml | python xmlgrep.py -v stuff

Решение Баша

Вот реализация того же базового алгоритма на bash. Он использует флаги для отслеживания того, принадлежит ли текущая строка XML-документу, и использует временный файл в качестве буфера для хранения каждого XML-документа по мере его обработки.

#!/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}"

Вот как можно запустить скрипт для поиска строки 'stuff':

bash xmlgrep.sh data.xml 'stuff'

А вот соответствующий вывод:

<a>
  <b>
    string to search for: stuff
  </b>
</a>

Вот как можно запустить скрипт для поиска строки 'øæå':

bash xmlgrep.sh data.xml 'øæå'

А вот соответствующий вывод:

<x>
    unicode string: øæå
</x>

Решение 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] ">";
        }
    }
}

Вот как бы вы это назвали:

gawk -v PATTERN="øæå" -f xmlgrep.awk data.xml

А вот соответствующий вывод:

<x>
    unicode string: øæå
</x>
1
04.01.2020, 12:31
1 ответ

Вы можете сделать это, сохранив числа в массиве, который вы выводите в блоке END:

{
    $1 = ""
    count[++n] = gsub("o", "")
}

END {
    $0 = ""                   # empty the current record
    for (i = 1; i <= n; ++i)
        $i = count[i]         # create new fields for output

    # output the current record (created above)
    print
}

Либо можно вывести числа по мере их вычисления, при необходимости разделив их разделителем полей, а затем закончить строку в блоке END:

{
    $1 = ""

    # Print the number, preceded by OFS if this is not the first line.
    # Does not output a newline (ORS).
    printf "%s%s", (NR > 1 ? OFS : ""), gsub("o", "")
}

END {
    # Finish the output (ORS is a newline by default).
    printf "%s", ORS
}

Оба этих варианта позволяют пользователю установить для OFSи ORSсоответствующие им значения в командной строке, например.

$ awk -v OFS=',' -f script.awk file
2,1
$ awk -v OFS='\t' -f script.awk file
2       1
$ awk -v OFS='-->' -v ORS='.\n' -f script.awk file
2-->1.

Примечание. :Я предполагаю, что вывод, показанный в вопросе, неверен, поскольку это то, что вы получите, если не игнорировать первое поле (и игнорировать регистрoписьмо).

1
27.01.2020, 23:40

Теги

Похожие вопросы