Читать построчно и печатать играм построчно

Я новичок в скриптах оболочки, было бы здорово, если я могу получить некоторую помощь с вопросом ниже.

Я хочу прочитать текстовый файл строка за строкой и распечатать все совпадающие шаблоны в этой строке в строке в новом текстовом файле.

например:

$ cat input.txt

SYSTEM ERROR: EU-1C0A  Report error -- SYSTEM ERROR: TM-0401 DEFAULT Test error
SYSTEM ERROR: MG-7688 DEFAULT error -- SYSTEM ERROR: DN-0A00 Error while getting object -- ERROR: DN-0A52 DEFAULT Error -- ERROR: MG-3218 error occured in HSSL
SYSTEM ERROR: DN-0A00 Error while getting object -- ERROR: DN-0A52 DEFAULT Error
SYSTEM ERROR: EU-1C0A  error Failed to fill in test report -- ERROR: MG-7688

предполагаемый выход выглядит следующим образом:

$ cat output.txt

EU-1C0A TM-0401
MG-7688 DN-0A00 DN-0A52 MG-3218
DN-0A00 DN-0A52
EU-1C0A MG-7688

я попробовал следующий код:

while read p; do
    grep -o '[A-Z]{2}-[A-Z0-9]{4}' | xargs
done < input.txt > output.txt

, который произвел этот выход:

EU-1C0A TM-0401 MG-7688 DN-0A00 DN-0A52 MG-3218 DN-0A00 DN-0A52 EU-1C0A MG-7688 .......

затем я также попытался это:

while read p; do
    grep -o '[A-Z]{2}-[A-Z0-9]{4}' | xargs > output.txt
done < input.txt

но не помогло :(

может быть, есть другой способ, я открыт для awk / sed / cut или что-то еще... :)

Примечание: может быть любое количество кодов ошибок (например, XX: XXXX, шаблон интереса в одной строке).

8 ответов


всегда есть perl! И это захватит любое количество матчей на линию.

perl -nle '@matches = /[A-Z]{2}-[A-Z0-9]{4}/g; print(join(" ", @matches)) if (scalar @matches);' output.txt

-e perl-код для запуска компилятором и -n выполнить одну строку за раз и -l автоматически пережевывает строку и добавляет новую строку к печатям.

регулярное выражение неявно совпадает с $_. Так что @matches = $_ =~ //g слишком многословно.

если нет совпадения, это ничего не напечатает.


% awk 'BEGIN{RS=": "};NR>1{printf "%s%s", , (~/\n/)?"\n":" "}' input.txt 
EU-1C0A TM-0401
MG-7688 DN-0A00 DN-0A52 MG-3218
DN-0A00 DN-0A52
EU-1C0A MG-7688

объяснение в longform:

awk '
    BEGIN{ RS=": " } # Set the record separator to colon-space
    NR>1 {           # Ignore the first record
        printf("%s%s", # Print two strings:
            ,      # 1. first field of the record (``)
            (~/\n/) ? "\n" : " ")
                     # Ternary expression, read as `if condition (thing
                     # between brackets), then thing after `?`, otherwise
                     # thing after `:`.
                     # So: If the record () matches (`~`) newline (`\n`),
                     # then put a newline. Otherwise, put a space.
    }
' input.txt 

предыдущий ответ на неизмененный вопрос:

% awk 'BEGIN{RS=": "};NR>1{printf "%s%s", , (NR%2==1)?"\n":" "}' input.txt 
EU-1C0A TM-0401
MG-7688 MG-3218
DN-0A00 DN-0A52
EU-1C0A MG-7688

edit: С гарантией от :-впрыска (thx @e0k). Проверяет, что первое поле после разделителя записей выглядит так, как мы ожидаем.

awk 'BEGIN{RS=": "};NR>1 &&  ~ /^[A-Z]{2}-[A-Z0-9]{4}$/ {printf "%s%s", , (~/\n/)?"\n":" "}' input.txt

вы всегда можете держать его очень просто:

$ awk '{o=""; for (i=1;i<=NF;i++) if ($i=="ERROR:") o=o$(i+1)" "; print o}' input.txt
EU-1C0A TM-0401
MG-7688 DN-0A00 DN-0A52 MG-3218
DN-0A00 DN-0A52
EU-1C0A MG-7688

вышеуказанное добавит пустой символ в конец каждой строки, тривиально избегаемый, если вы заботитесь...


чтобы сохранить ваш grep шаблон, вот так:

while IFS='' read -r p; do
    echo $(grep -o '[A-Z]\{2\}-[A-Z0-9]\{4\}' <<<"$p")
done < input.txt > output.txt
  • while IFS='' read -r p; do - стандартный способ чтения строки за строкой в переменную. См., например, ответ.
  • grep -o '[A-Z]\{2\}-[A-Z0-9]\{4\}' <<<"$p" запускает grep и печатает матчей. The <<<"$p" это "вот строку" это обеспечивает строку $p (строка, которая была прочитана) как stdin to grep. Это значит grep поиск содержание $p и печать каждого матча на его своя линия.
  • echo $(grep ...) преобразует переводы строк в grepвыводится в пробелы и добавляет новую строку в конце. Поскольку этот цикл происходит для каждой строки, результатом является печать совпадений каждой входной строки на одной строке вывода.
  • done < input.txt > output.txt правильно: вы предоставляете вход и выход из цикла в целом. Вам не нужно перенаправление внутри цикла.

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

cat input.txt | grep -o '[A-Z]\{2\}-[A-Z0-9]\{4\}' | xargs -L2 > output.txt

вот решение с awk, которое довольно просто, но это не элегантный однострочный (как правило, многие решения awk). Он должен работать с любым количеством ваших кодов ошибок в строке и с кодом ошибки, определенным как поле (разделенное пробелом слово), которое соответствует данному регулярному выражению. Поскольку это не шикарный однострочный, я сохранил программу в файле:

кодексы.на awk

#!/usr/bin/awk -f
{
    m=0;
    for (i=1; i<=NF; ++i) {
        if ( $i ~ /^[A-Z][A-Z]-[A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9]$/ ) {
            if (m>0) printf OFS
            printf $i
            m++
        }
    }
    if (m>0) printf ORS
}

вы бы запустили это как

$ awk -f codes.awk input.txt

я надеюсь, что вы найдете его довольно легко читать. Он запускает блок один раз для каждой строки ввода. Он выполняет итерацию по каждому полю и проверяет, соответствует ли оно регулярному выражению, а затем печатает поле. Переменная m отслеживает количество совпавших полей в текущей строке до сих пор. Целью этого является печать разделителя выходного поля OFS (пробел по умолчанию) между сопоставленные поля по мере необходимости и использовать выход разделитель записи ORS (новая строка по умолчанию), только если был найден хотя бы один код ошибки. Это предотвращает ненужное пустое пространство.

обратите внимание, что я изменил регулярное выражение с [A-Z]{2}-[A-Z0-9]{4} to [A-Z][A-Z]-[A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9]. Это потому, что старый awk не будет (или по крайней мере не может) интервальные выражения (the {n} запасные части). Вы могли бы использовать [A-Z]{2}-[A-Z0-9]{4} С


извлечение grep -n С AWK

grep -n -o '[A-Z]\{2\}-[A-Z0-9]\{4\}' file | awk -F: -vi=0 '{
  printf("%s%s", i ? (i ==  ? " " : "\n") : "", )
  i = 
}'

идея присоединиться к линии с выхода grep -n:

1:EU-1C0A
1:TM-0401
2:MG-7688
2:DN-0A00
2:DN-0A52
2:MG-3218
3:DN-0A00
3:DN-0A52
4:EU-1C0A
4:MG-7688

по номерам строк. AWK инициализирует разделитель (-F:) и i переменной (-vi=0), затем обрабатывает вывод grep командная строка за строкой.

это печать символ, в зависимости от условное выражение, которая проверяет значение первого поля . Если i равно нулю (первый шаг), он печатает только второе поле . В противном случае, если первое поле равно i, он печатает пробел, иначе новая строка ("\n"). После пробела/новой строки печатается второе поле.

после печати следующего фрагмента значение первого поля сохраняется в i для следующих итераций (строк):i = .

Perl

извлечение grep -n in На Perl

use strict;
use warnings;

my $p = 0;

while (<>) {
  /^(\d+):(.*)$/;
  print $p ==  ? " " : "\n" if $p;
  print ;
  $p = ;
}

использование: grep -n -o '[A-Z]\{2\}-[A-Z0-9]\{4\}' file | perl script.pl.

Один Строка

но Perl на самом деле настолько гибкий и мощный, что вы можете полностью решить проблему с помощью одной строки:

perl -lne 'print @_ if @_ = /([A-Z]{2}-[A-Z\d]{4})/g' < file

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

одной из ключевых идей является использование -l переключатель

  1. автоматически chomps разделитель записи входного сигнала $/;
  2. назначает разделитель выходной записи $\ значение $/ (который является новой строкой по умолчанию)

значение разделителя выходной записи, если определено, печатается после последнего аргумента, переданного в print. В результате скрипт печатает все совпадения (@_, в частности), за которым следует перевод строки.

на @_ переменной обычно используется как массив параметры подпрограммы. Я использовал его в сценарии только ради краткости.


в Gnu awk. Поддерживает несколько матчей на каждой записи:

$ awk '
{
    while(match(, /[A-Z]{2}-[A-Z0-9]{4}/)) {  # find first match on record
        b=b substr(,RSTART,RLENGTH) OFS       # buffer the match
        =substr(,RSTART+RLENGTH)            # truncate from start of record
    }
    if(b!="") print b                           # print buffer if not empty
    b=""                                        # empty buffer
}' file
EU-1C0A TM-0401 
MG-7688 DN-0A00 DN-0A52 MG-3218 
DN-0A00 DN-0A52 
EU-1C0A MG-7688 

недостаток: в конце каждой печатной записи будет дополнительный OFS.

Если вы хотите использовать другие неловко, чем GNU awk и заменить регулярное выражение match С:

while(match(, /[A-Z][A-Z]-[A-Z0-9][A-Z0-9][A-Z0-9]/))