Итак, я пытаюсь написать простой скрипт, который будет анализировать файл xml и перенаправлять вывод в новый файл на основе имени категории, в которой он был найден. Например, так выглядит файл XML.

<category> Music </Category>
<url>https://www.youtube.com/watch?v=waAlgFq9Xq8</url>
<category> Movies </Category>
<url>https://www.youtube.com/watch?v=g4U4BQW9OEk</url>

Мой сценарий выглядит примерно так:

for i in *.xml; do
    name=$(grep -i "<category>" $i | awk '{print $1}')
    line=$(grep -i -A1 "<category>" $i)
    echo "$line" >> $filename
done

Так, например, Movies.log будет содержать все ссылки, найденные в категории «Фильмы», а Music.log будет содержать все ссылки, найденные в категории «Музыка».

1
Jeff Schaller 28 Янв 2020 в 16:36

2 ответа

Лучший ответ

Рассматривали ли вы цикл по каждой категории? Нравится:

for i in *.xml; do
    for category in $(sed -rn '/^<category>/{s/[^>]*> *([^ <]*).*/\1/p}' "$i"); do
        sed -rn "/^<category> *$category/,/^<category>/{s/<url> *([^ <]*).*/\1/p}" "$i" > "$category.log"
    done
done

Обновление: использование awk

awk -v 'RS=<' -v 'cat=none' -F '>' \
'$1 ~ /^category$/ {gsub(/^ *| *$/,"",$2); cat=$2} \
$1 ~ /^url$/ {print $2 >> cat".log"}' \
*.xml
  • Это позволяет избежать зацикливания входных файлов и будет добавляться к файлу .log для любой категории.

  • Использование назначения разделителя записей awk -v 'RS=<' будет означать, что тег категории/url будет найден где угодно (а не только в начале строки). Новая строка может появиться в любом месте данных xml.

  • Сочетание этого с установкой разделителя полей на '>' означает, что первое поле каждой записи будет эквивалентно имени тега xml.

  • Каждый раз, когда awk встречает запись, где первым полем является «категория», переменной cat присваивается имя этой категории.

  • Когда awk встречает запись, где первым полем является «url», он добавит этот URL-адрес в файл cat.log.

  • cat будет определен как none для начала. Это предотвращает сбой в случае, если встречается <url> без каких-либо предшествующих <category>.

  • Замена gsub(/^ *| *$/,"",$2) предназначена для удаления начальных/конечных пробелов для имен категорий, которые появляются в вашем примере входного файла .xml.


Заметка:

Ничто из вышеперечисленного не является надежным. Для правильных входных файлов xml лучше использовать настоящий парсер xml, например xmlstarlet. Но для этого также потребуются правильно сформированные xml-файлы (например, в примере ввода нет соответствующих тегов <category>).

2
Community 11 Июн 2020 в 17:16

Я подготовил это решение:

grep -hP "<category.*>|<url.*>" *.xml | cut -d ">" -f 2 | cut -d "<" -f 1 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' | gawk 'BEGIN { category = ""; } { if (!length($0)) { next; } if (length(category)) { printf("\necho -e \"%s\" >> \"%s.log\"", $0, category); category = ""; } else { category = $0; } } END { printf("\n"); }' | bash

Он ищет все файлы .xml в текущем каталоге и добавляет URL-адрес к файлу, названному в соответствии с категорией, найденной в строке, предшествующей URL-адресу (вы можете проверить вывод, удалив | bash в конце) .

Извлекать узлы XML только для интересующих нас данных

Позволив grep (например) искать шаблон в файлах с именами *.xml, нам не нужно перебирать имена файлов. Параметр -h для команды grep не позволяет отображать имена файлов в выводе. Шаблон, переданный grep, представляет собой Perl-совместимое регулярное выражение (-P).

Извлечь значения для интересующих нас узлов

Строки, возвращаемые командой grep, выглядят примерно так:

    <category> MyMusic </category>
    <url>https://www.youtube.com/watch?v=waAlgFq9Xq8123</url>
    <category> MyMovies </category>
    <url>https://www.youtube.com/watch?v=g4U4BQW9OEk456</url>
    <category>Music</category>
    <url>https://www.youtube.com/watch?v=waAlg</url>
    <category>              Music </category>
    <url>https://www.youtube.com/watch?v=waAlgFq9Xq8</url>
    <category> Movies </category>
    <url>https://www.youtube.com/watch?v=g4U4BQW9OEk</url>

Мы только что отфильтровали строки с данными, которые нам не нужны. Теперь нам нужно извлечь значения внутри узлов, что сводится к извлечению данных между открывающим и закрывающим тегами, т.е. между знаками > и < (мы не неважно, какой это узел, поэтому мы используем «общий» подход).

Этого легко добиться с помощью | cut -d ">" -f 2 | cut -d "<" -f 1

Что практически означает взять все с правой стороны (-f 2) от знака >, затем, с новым результатом, который у нас есть, взять все с левой стороны (-f 1) от знака <

Что оставляет нас ниже

 MyMusic 
https://www.youtube.com/watch?v=waAlgFq9Xq8123
 MyMovies 
https://www.youtube.com/watch?v=g4U4BQW9OEk456
Music
https://www.youtube.com/watch?v=waAlg
                Music 
https://www.youtube.com/watch?v=waAlgFq9Xq8
 Movies 
https://www.youtube.com/watch?v=g4U4BQW9OEk

Теперь нам нужно обрезать значения, здесь идет небольшой корректирующий шаг.

Обрезать значения

Обрезка начальных и конечных пробелов с помощью | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'

С помощью -e sed может выполнять сценарии в том порядке, в котором они заданы, без необходимости передавать дополнительную команду sed (или несколько команд sed для других сценариев).

Первый скрипт, переданный в sed, обрезает начальный пробел (т. е. любые символы [:space:] в начале строки (@ каждую строку)), а второй обрезает конечный пробел (т. е. любые символы [:space:] перед концом строки). строки (@ каждой строки).

Теперь у нас есть что-то вроде следующего, и мы почти закончили:

MyMusic
https://www.youtube.com/watch?v=waAlgFq9Xq8123
MyMovies
https://www.youtube.com/watch?v=g4U4BQW9OEk456
Music
https://www.youtube.com/watch?v=waAlg
Music
https://www.youtube.com/watch?v=waAlgFq9Xq8
Movies
https://www.youtube.com/watch?v=g4U4BQW9OEk

Записать команды добавления файла в стандартный вывод

Точно так же, как мы пишем эхо-команды для добавления данных в файл, нам нужно что-то, что автоматизирует процесс. Я решил продолжить с gawk. gawk считывает данные построчно и записывает категорию в переменную. Когда он читает другую строку, если переменная категории не пуста, то строка содержит URL-адрес. Используя эту технику, мы можем просто вводить такие команды, как echo -e "current url" >> current_category.log.

Обратите внимание на обязательное использование >> для добавления новых данных в файл. Использование > запишет только последний URL, и мы получим одну строку для каждой категории!

В результате мы только что записали данные, как показано ниже, в стандартный вывод:

echo -e "https://www.youtube.com/watch?v=waAlgFq9Xq8123" >> "MyMusic.log"
echo -e "https://www.youtube.com/watch?v=g4U4BQW9OEk456" >> "MyMovies.log"
echo -e "https://www.youtube.com/watch?v=waAlg" >> "Music.log"
echo -e "https://www.youtube.com/watch?v=waAlgFq9Xq8" >> "Music.log"
echo -e "https://www.youtube.com/watch?v=g4U4BQW9OEk" >> "Movies.log"

Передайте команды добавления данных в bash для выполнения

Последний элемент конвейера, | bash, обеспечивает передачу эхо-команд в bash для выполнения.

Обратите внимание, что gawk может записывать/добавлять данные в файлы. Но я намеренно хотел иметь наименьший возможный gawk-скрипт.

0
JMW 30 Янв 2020 в 11:47