Извлечь часть текстового файла от первого вхождения одной строки до первого вхождения другой

592
dgig

Как извлечь часть большого текстового файла, начиная с первого появления FOO и заканчивая первым появлением BAR?

В моем случае я пытаюсь извлечь часть файла sql, созданного mysqldump.

1
Надеюсь, это поможет `sed -n '/ FOO /, / BAR / 'file` Paulo 6 лет назад 2

2 ответа на вопрос

2
simlev

Кредиты @dgig и @Paulo, которые помогли мне с их отзывами! Финальный perl однострочник здесь:

perl -lne 'if(/FOO/../BAR/)' file 

Объяснение:

if(/FOO/../BAR/){ # perform the following actions on each line, starting # with a line that contains FOO, and up to and including # a line that contains BAR  s/.*?(FOO)/$1/ if!$i++; # only on the first line that contains FOO, # delete all characters before FOO  s/BAR\K.*//&&print&&exit;# if the line contains BAR, remove characters # after BAR, print the line and stop processing  print # simply print the line contents 

Старый ответ:

Кредиты @Paulo для простого sedрешения. Это так же просто и легко прочитать в awk:

awk '/FOO/,/BAR/' file 

Однако это может быть слишком просто: он возвращает целые строки, а не точно «часть текста, начинающуюся при первом появлении FOO и заканчивающуюся при первом появлении BAR». Я считаю, что это означает, что FOO должно быть первым словом, а BAR - последним. Для того, чтобы сделать это, требуется более запутанный ответ. Позвольте мне попытаться сделать это в perl.

Простой случай (возвращает целые строки):

perl -lne 'print if /FOO/../BAR/' file 

Сложный случай (именно от FOO до BAR):

perl -lne 'if(/FOO/../BAR/){$_=~s/.*?(FOO)/$1/ if!$i++;$_=~s/BAR\K.*//;print}' file 

Мне нравится это эквивалентное решение, которое присваивает переменную оператору диапазона:

perl -lne 'if($a=/FOO/../BAR/){$_=~s/.*?(FOO)/$1/ if$a==1;$_=~s/BAR\K.*// if$a=~/E/;print}' file 

Примечание. Предполагается, что извлекается только одна часть текста, то есть мы не должны встречать другое FOO после первого абзаца, разделенного FOO и BAR.

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

awk '/FOO/,/BAR/ }' file 

и в perl:

perl -lne '(print&&/BAR/&&exit) if /FOO/../BAR/' file 

И сложные, более изысканные решения становятся:

perl -lne 'if(/FOO/../BAR/){$_=~s/.*?(FOO)/$1/ if!$i++;$_=~s/BAR\K.*//&&print&&exit;print}' file 

а также:

perl -lne 'if($a=/FOO/../BAR/){$_=~s/.*?(FOO)/$1/ if$a==1;$_=~s/BAR\K.*//&&print&&exit if$a=~/E/;print}' file 

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

Спасибо за эти ответы, к сожалению, в моем случае это не работает - он запускает выходной файл в нужной точке, но затем проходит весь конец файла, не останавливаясь на «BAR». dgig 6 лет назад 0
Обновление: опробовал ваши решения на текст из бесплатных книг Гутенберга - в этом случае это работало без проблем. В моем случае, однако, большой файл sql, он сделал, как я описал в моем первом комментарии. Я не уверен, почему разница возникнет, хотя. dgig 6 лет назад 0
@dgid Спасибо за отзыв. Может ли это быть случай-чувствительность? Специальные символы? Пожалуйста, опубликуйте выписку, чтобы мы могли проверить, не является ли это темным делом, о котором я не думал. Мне трудно сказать иначе. simlev 6 лет назад 0
@simlev Вы правы, я не думал о первом появлении, я думал о линии, которая имеет первое вхождение. Ваша интерпретация ближе к запросу OP. В sed я думаю, что это можно сделать, что является тяжелой работой без регулярного выражения Perl. Что касается `awk`, я думаю, что вы должны выйти сразу после печати строки с помощью` BAR`, или awk напечатает любой другой блок с этими словами, как это делает `sed`. Paulo 6 лет назад 0
@Paulo Спасибо за отзыв, вы правы. simlev 6 лет назад 0
@dgid Пожалуйста, проверьте, есть ли второй FOO после первого BAR: согласно моему «Примечание:» и как правильно указал Пауло, эта возможность не принимается во внимание в моих решениях. simlev 6 лет назад 0
@Simlev привет! Спасибо за все это. Да, это слово определенно встречается второй раз, так что, возможно, это объясняет это. Мой файл - это файл mysqldump, похожий на этот общий пример, который я нашел (правда, около 1G). https://github.com/mrdavidlaing/pressupbox-development-boilerplate/blob/master/tests/wordpress.sample.sql dgig 6 лет назад 0
@simlev и Paulo - потрясающе - обновленный perl one liner отлично работает - большое спасибо! Экономия времени. dgig 6 лет назад 0
2
Paulo

В этом случае это было не так сложно, я думал, что это может быть. С sed, от первого появления FOO до первого появления BAR (я не пробовал, но, вероятно, что-то вроде второго FOO для второго BAR было бы более трудным).

sed -nr '/FOO/ { /FOO/ s/[^F]+FOO/FOO/p :a n /BAR/ s/([^B]+BAR).*/\1/ p /BAR/ q ba }' <<<'line1 > line2 FOO text1 FOO text2 > line3 > line4 BAR text3 BAR text4 > line5'  FOO text1 FOO text2 line3 line4 BAR 
Проголосовал, даже если он не работает, как ожидалось, если есть F перед FOO. Жадное ограничение. Я хотел бы спросить вас, почему вы не пошли с более простой `/ BAR / s / (BAR). * / \ 1 /` для второй замены. simlev 6 лет назад 1
@simlev О `/ BAR / s / (BAR). * / \ 1 /` Вы правы, это проще и работает. Я думаю, что я просто повторил подстановку из строки `/ FOO /`, я не осознавал, что в этом нет необходимости. О том, есть ли `F` перед` FOO`, вы тоже правы, но на этот раз я знаю;) Я думаю, что в `sed`, чтобы отрицать слово, нужно отрицать char с помощью char` sed -nr ' s / ([^ F] | F [^ O] | FO [^ O]) + // p '<<<' line2 Foo text FOO text1 FOO text2'` Paulo 6 лет назад 0