Массив сценария Bash, разделенный вертикальной чертой и скобкой

Я собираюсь предположить, что то, что вы разместили, является образцом, потому что это не действительный XML. Если это предположение неверно, мой ответ не подходит... но если это так, вам действительно нужно ударить человека, который дал вам XML, свернутой копией спецификации XML и потребовать, чтобы он "исправил это".

Но на самом деле - awk и регулярные выражения не являются подходящим инструментом для этой работы. XML-парсер - это то, что нужно. А с парсером сделать то, что вы хотите, абсурдно просто:

#!/usr/bin/env perl

use strict;
use warnings;

use XML::Twig; 

#parse your file - this will error if it's invalid. 
my $twig = XML::Twig -> new -> parsefile ( 'your_xml' );
#set output format. Optional. 
$twig -> set_pretty_print('indented_a');

#iterate all the 'record' nodes off the root. 
foreach my $record ( $twig -> get_xpath ( './record' ) ) {
   #if - beneath this record - we have a node anywhere (that's what // means)
   #with a tag of 'keyword' and content of 'SEARCH' 
   #print the whole record. 
   if ( $record -> get_xpath ( './/keyword[string()="SEARCH"]' ) ) {
       $record -> print;
   }
}

xpath очень похож на регулярные выражения - в некотором смысле - но он больше похож на путь к каталогу. Это означает, что он учитывает контекст и может работать с XML-структурами.

В приведенном выше примере: ./ означает "ниже текущего узла", поэтому:

$twig -> get_xpath ( './record' )

Означает любые теги "верхнего уровня" <запись>.

Но .// означает "на любом уровне, ниже текущего узла", поэтому он будет делать это рекурсивно.

$twig -> get_xpath ( './/search' ) 

Получит любые узлы на любом уровне.

Квадратные скобки обозначают условие - это либо функция (например, text() для получения текста узла), либо вы можете использовать атрибут. Например, //category[@name] найдет любую категорию с атрибутом name, а //category[@name="xyz"] отфильтрует их дальше.

XML, используемый для тестирования:

<XML>
<record category="xyz">
<person ssn="" e-i="E">
<title xsi:nil="true"/>
<position xsi:nil="true"/>
<details>
<names>
<first_name/>
<last_name></last_name>
</names>
<aliases>
<alias>CDP</alias>
</aliases>
<keywords>
<keyword xsi:nil="true"/>
<keyword>SEARCH</keyword>
</keywords>
<external_sources>
<uri>http://www.google.com</uri>
<detail>SEARCH is present in abc for xyz reason</detail>
</external_sources>
</details>
</person>
</record>
<record category="abc">
<person ssn="" e-i="F">
<title xsi:nil="true"/>
<position xsi:nil="true"/>
<details>
<names>
<first_name/>
<last_name></last_name>
</names>
<aliases>
<alias>CDP</alias>
</aliases>
<keywords>
<keyword xsi:nil="true"/>
<keyword>DONTSEARCH</keyword>
</keywords>
<external_sources>
<uri>http://www.google.com</uri>
<detail>SEARCH is not present in abc for xyz reason</detail>
</external_sources>
</details>
</person>
</record>
</XML>

Выходные данные:

 <record category="xyz">
    <person
        e-i="E"
        ssn="">
      <title xsi:nil="true" />
      <position xsi:nil="true" />
      <details>
        <names>
          <first_name/>
          <last_name></last_name>
        </names>
        <aliases>
          <alias>CDP</alias>
        </aliases>
        <keywords>
          <keyword xsi:nil="true" />
          <keyword>SEARCH</keyword>
        </keywords>
        <external_sources>
          <uri>http://www.google.com</uri>
          <detail>SEARCH is present in abc for xyz reason</detail>
        </external_sources>
      </details>
    </person>
  </record>

Примечание - вышеприведенное просто печатает запись в STDOUT. На самом деле... на мой взгляд, это не самая лучшая идея. Не в последнюю очередь потому, что - это не печатает структуру XML, и поэтому это не совсем "правильный" XML, если у вас более одной записи (нет "корневого" узла).

Поэтому я бы вместо этого - для достижения именно того, о чем вы спрашиваете:

#!/usr/bin/env perl

use strict;
use warnings;

use XML::Twig; 

my $twig = XML::Twig -> new -> parsefile ('your_file.xml'); 
$twig -> set_pretty_print('indented_a');

foreach my $record ( $twig -> get_xpath ( './record' ) ) {
   if ( not $record -> findnodes ( './/keyword[string()="SEARCH"]' ) ) {
       $record -> delete;
   }
}

open ( my $output, '>', "output.txt" ) or die $!;
print {$output} $twig -> sprint;
close ( $output ); 

Это вместо - инвертирует логику, и удаляет (из разобранной структуры данных в памяти) записи, которые вам не нужны, и печатает всю новую структуру (включая заголовки XML) в новый файл под названием "output.txt".

0
28.03.2019, 02:30
2 ответа

Вы можете сделать это с помощью примера скрипта (мой не оптимален, но будет работать)

awk -F'[]|]' '{
   print $1,$2,$3,$4,$7,$10
   print $1,$2,$3,$5,$8,$10
   print $1,$2,$3,$6,$9,$10 }' input_filename

Или

awk -F'[]|]' '{
   for (i = 4; i <= 6; i++)
     print $1,$2,$3,$i,$(i+3),$10}' input_filename

Вы можете изменить разделитель полей вывода(пробел по умолчанию ), добавив -v OFS=','.

И благодаря @steeldriver еще один гибкий способ (с внутренним разделением полей )для выполнения задания:

awk -F'|' '{
  split($3,a,/]/); n = split($4,b,/]/); 
  for(i=1;i<=n;i++) print $1,$2,a[1],a[i+1],b[i],$5}' input_filename

В соответствии с отредактированным вопросом, если вы хотите заменить пустое поле0(нулем ), вы можете сделать это с помощью скрипта, например:

awk -F'[]|]' '{ 
   for (i = 1; i <= 11; i++) if ($i == "") $i=0}
   {
   print $1,$2,$3,$4,$7,$10,$11
   print $1,$2,$3,$5,$8,$10,$11
   print $1,$2,$3,$6,$9,$10,$11 }' input_filename

Судя по вашему комментарию, скрипт должен выглядеть так:

awk -F'|' -v OFS="\t" '{
 n = split($4,D,"]"); split($5,E,"]");
 for (i = 1; i <= n; i++) {
     if (D[i] == "") D[i]=0;
     if (E[i] == "") E[i]=0;}
     print $1,$2,$3,D[i],E[i],$6,$7 }' input_file 
5
28.01.2020, 02:13

Другой awkвзгляд на проблему:

awk -F'|' '
  {
    n = split($4, f4, "]")
        split($3, f3, "]")
    for (i = 1; i <= n; i++) {
      $3 = f3[1] OFS f3[1+i]
      $4 = f4[i]
      print
    }
  }'
4
28.01.2020, 02:13

Теги

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