Читать построчно и печатать играм построчно
Я новичок в скриптах оболочки, было бы здорово, если я могу получить некоторую помощь с вопросом ниже.
Я хочу прочитать текстовый файл строка за строкой и распечатать все совпадающие шаблоны в этой строке в строке в новом текстовом файле.
например:
$ 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
togrep
. Это значит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
переключатель
- автоматически chomps разделитель записи входного сигнала
$/
; - назначает разделитель выходной записи
$\
значение$/
(который является новой строкой по умолчанию)
значение разделителя выходной записи, если определено, печатается после последнего аргумента, переданного в 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]/))