Я написал решение для 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, который делает то, что вы хотите:
#!/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
не очень хорошее, так что оно довольно грубое. Он использует ту же основную идею, что и сценарии 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>
Вы можете сделать это, сохранив числа в массиве, который вы выводите в блоке 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
письмо).