Как получить часть файла после первой строки, которая соответствует регулярному выражению?

169

У меня есть файл с около 1000 строк. Мне нужна часть моего файла после строки, которая соответствует моему выражению grep.

То есть:

$ cat file | grep 'TERMINATE'     # It is found on line 534

Итак, я хочу файл из строки 535 в строку 1000 для дальнейшей обработки.

Как я могу это сделать?

Югаль Джиндл
источник
34
UUOC (Бесполезное использование кошки):grep 'TERMINATE' file
Джейкоб
30
Я знаю это, как будто я использую это таким образом. Вернемся к вопросу.
Югаль Джиндл
3
Это очень хороший вопрос программирования, и он хорошо подходит для стекового потока.
Aioobe
13
@Jacob Это не бесполезное использование кошки вообще. Он используется для печати файла в стандартный вывод, что означает, что мы можем использовать grepстандартный интерфейс ввода для чтения данных, а не узнавать, к какому переключателю обращаться grep, и sed, и awk, и pandoc, и ffmpegт. Д., Когда мы хотим прочитать из файла. Это экономит время, потому что нам не нужно изучать новый переключатель каждый раз, когда мы хотим сделать то же самое: читать из файла.
Runeks
@runeks Я согласен с твоими чувствами - но ты можешь достичь этого без кота grep 'TERMINATE' < file. Может быть, это делает чтение немного сложнее - но это сценарии оболочки, так что это всегда будет проблемой :)
LOAS

Ответы:

307

Далее будет напечатана строка соответствия TERMINATEдо конца файла:

sed -n -e '/TERMINATE/,$p'

Объяснено: -n отключает поведение по умолчанию sedпри печати каждой строки после выполнения сценария на нем, -eуказывает сценарий на sed, /TERMINATE/,$это выбор диапазона адресов (строк), означающий, что первая строка соответствует TERMINATEрегулярному выражению (например, grep) до конца файла ( $) и p- команда печати, которая печатает текущую строку.

Это напечатает от строки, которая следует за соответствующей строкой TERMINATEдо конца файла:
(от ПОСЛЕ соответствующей строки к EOF, НЕ включая соответствующую строку)

sed -e '1,/TERMINATE/d'

Объяснено: 1,/TERMINATE/ это выбор диапазона адресов (строк), означающий первую строку для ввода в 1-ю строку, соответствующую TERMINATEрегулярному выражению, и dэто команда удаления, которая удаляет текущую строку и переходит к следующей строке. Так как по sedумолчанию выполняется печать строк, он будет печатать строки после TERMINATE конца ввода.

Редактировать:

Если вы хотите строки раньше TERMINATE:

sed -e '/TERMINATE/,$d'

И если вы хотите обе строки до и после TERMINATEв 2 разных файлах за один проход:

sed -e '1,/TERMINATE/w before
/TERMINATE/,$w after' file

Файлы before и after будут содержать строку с terminate, поэтому для обработки каждого из них вам необходимо использовать:

head -n -1 before
tail -n +2 after

Edit2:

Если вы не хотите жестко кодировать имена файлов в сценарии sed, вы можете:

before=before.txt
after=after.txt
sed -e "1,/TERMINATE/w $before
/TERMINATE/,\$w $after" file

Но тогда вы должны избежать $значения последней строки, чтобы оболочка не пыталась расширить $wпеременную (обратите внимание, что теперь мы используем двойные кавычки вокруг сценария вместо одинарных кавычек).

Я забыл сказать, что новая строка важна после имен файлов в скрипте, так что sed знает, что имена файлов заканчиваются.


Изменить: 2016-0530

Себастьян Клеман спросил: «Как бы вы заменили жестко закодированную TERMINATEпеременную?»

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

matchtext=TERMINATE
before=before.txt
after=after.txt
sed -e "1,/$matchtext/w $before
/$matchtext/,\$w $after" file

использовать переменную для сопоставления текста с предыдущими примерами:

## Print the line containing the matching text, till the end of the file:
## (from the matching line to EOF, including the matching line)
matchtext=TERMINATE
sed -n -e "/$matchtext/,\$p"
## Print from the line that follows the line containing the 
## matching text, till the end of the file:
## (from AFTER the matching line to EOF, NOT including the matching line)
matchtext=TERMINATE
sed -e "1,/$matchtext/d"
## Print all the lines before the line containing the matching text:
## (from line-1 to BEFORE the matching line, NOT including the matching line)
matchtext=TERMINATE
sed -e "/$matchtext/,\$d"

Важными моментами о замене текста переменными в этих случаях являются:

  1. Переменные ( $variablename), заключенные в single quotes[ '], не будут «расширяться», но переменные внутри double quotes[ "] будут. Таким образом, вы должны изменить все , single quotesчтобы , double quotesесли они содержат текст , который вы хотите заменить переменную.
  2. В sedдиапазонах также содержат $и сразу же следуют буква , как: $p, $d, $w. Они также будут выглядеть как переменные , которые будут расширены, так что вы должны избежать этих $символов с обратной косой черты [ \] , как: \$p, \$d, \$w.
jfg956
источник
Как мы можем получить строки перед TERMINATE и удалить все последующие?
Югаль Джиндл
Как бы вы заменили жестко закодированный ТЕРМИНАЛ на переменную?
Себастьян Клеман,
2
Один из вариантов использования, который здесь отсутствует, - это печать строк после последнего маркера (если в файле их может быть несколько ... подумайте о файлах журнала и т. Д.).
Мат
Пример sed -e "1,/$matchtext/d"не работает, когда $matchtextпроисходит в первой строке. Я должен был изменить это на sed -e "0,/$matchtext/d".
Каралга
61

В качестве простого приближения вы можете использовать

grep -A100000 TERMINATE file

который ищет TERMINATEи выводит до 100000 строк после этой строки.

С man страницы

-A NUM, --after-context=NUM

Вывести NUM строк конечного контекста после сопоставления строк. Помещает строку, содержащую разделитель групп (-), между смежными группами совпадений. С опцией -o или --only-match это не имеет никакого эффекта, и выдается предупреждение.

aioobe
источник
Это может работать для этого, но мне нужно кодировать его в свой сценарий для обработки многих файлов. Итак, покажите какое-нибудь общее решение.
Югаль Джиндл
3
Я думаю, что это одно из практических решений!
Мишельготта
2
аналогично -B NUM, --before-context = NUM ​​Печатать NUM строк переднего контекста перед сопоставлением строк. Помещает строку, содержащую разделитель групп (-), между смежными группами совпадений. С опцией -o или --only-match это не имеет никакого эффекта, и выдается предупреждение.
PiyusG
это решение сработало для меня, потому что я могу легко использовать переменные в качестве моей строки для проверки.
Хосе Мартинес
3
Хорошая идея! Если вы не уверены в размере контекста, fileвместо этого вы можете считать следующие строки :grep -A$(cat file | wc -l) TERMINATE file
Лемминг
26

Инструмент для использования здесь - awk:

cat file | awk 'BEGIN{ found=0} /TERMINATE/{found=1}  {if (found) print }'

Как это работает:

  1. Мы устанавливаем переменную 'found' в ноль, оценивая false
  2. если совпадение для 'TERMINATE' найдено с регулярным выражением, мы устанавливаем его равным единице.
  3. Если наша переменная 'found' имеет значение True, выведите :)

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

Джос Де Грейв
источник
Простой, элегантный и очень общий. В моем случае он печатал все до второго появления «###»:cat file | awk 'BEGIN{ found=0} /###/{found=found+1} {if (found<2) print }'
Александр Стельмачонек
3
Инструмент не для использования здесь cat. awkвполне способен принимать одно или несколько имен файлов в качестве аргументов. См. Также stackoverflow.com/questions/11710552/useless-use-of-cat
tripleee
9

Если я правильно понимаю ваш вопрос, вы хотите строки после TERMINATE , не включая TERMINATE-line. awkможно сделать это простым способом:

awk '{if(found) print} /TERMINATE/{found=1}' your_file

Объяснение:

  1. Хотя это не лучшая практика, вы можете полагаться на тот факт, что все переменные по умолчанию имеют значение 0 или пустую строку, если она не определена. Поэтому первое выражение ( if(found) print) не будет печатать ничего, с чего можно начать.
  2. После того, как печать завершена, мы проверяем, является ли это стартовой линией (которая не должна быть включена).

Это будет печатать все строки , после в TERMINATE-LINE.


Обобщение:

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

Пример:

$ cat ex_file.txt 
not this line
second line
START
A good line to include
And this line
Yep
END
Nope more
...
never ever
$ awk '/END/{found=0} {if(found) print} /START/{found=1}' ex_file.txt 
A good line to include
And this line
Yep
$

Объяснение:

  1. Если найдена конечная строка, печать не производится. Обратите внимание, что эта проверка выполняется перед фактической печатью, чтобы исключить конечную строку из результата.
  2. Вывести текущую строку, если foundона установлена.
  3. Если начальная строка найдена, установите ее found=1так, чтобы были напечатаны следующие строки. Обратите внимание, что эта проверка выполняется после фактической печати, чтобы исключить начальную строку из результата.

Ноты:

  • Код опирается на тот факт, что по умолчанию все awk-vars имеют значение 0 или пустую строку, если она не определена. Это верно, но не может быть лучшей практикой, поэтому вы можете добавить BEGIN{found=0}в начало выражения awk.
  • Если найдено несколько начальных-конечных блоков, все они печатаются.
UlfR
источник
1
Удивительный Потрясающий пример. Просто провел 2 часа, глядя на csplit, sed и всевозможные сложные команды awk. Это не только сделало то, что я хотел, но и показало достаточно просто, чтобы понять, как его изменить, чтобы сделать несколько других связанных вещей, которые мне нужны. Заставляет меня помнить, что awk великолепен, и не только в неразборчивом беспорядке Спасибо.
user1169420
{if(found) print}Это немного анти-паттерн в awk, более идиоматично заменять блок просто foundили, found;если вам нужен другой фильтр впоследствии.
user000001
@ user000001 объясни пожалуйста. Я не понимаю, что заменить и как. В любом случае, я думаю, то, как написано, очень ясно дает понять, что происходит.
UlfR
1
Вы бы заменить awk '{if(found) print} /TERMINATE/{found=1}' your_fileс awk 'found; /TERMINATE/{found=1}' your_file, они оба должны делать то же самое.
user000001
7

Используйте расширение параметра bash следующим образом:

content=$(cat file)
echo "${content#*TERMINATE}"
Му Цяо
источник
Можете ли вы объяснить, что вы делаете?
Югаль Джиндл
Я скопировал содержимое файла в переменную $ content. Затем я удалил всех персонажей, пока «TERMINATE» не был замечен. Он не использовал жадное сопоставление, но вы можете использовать жадное сопоставление с помощью $ {content ## * TERMINATE}.
Му Цяо
вот ссылка на руководство по bash: gnu.org/software/bash/manual/…
Му Цяо
6
что произойдет, если размер файла составляет 100 ГБ?
Znik
1
Downvote: Это ужасно (чтение файла в переменную) и неправильно (использование переменной без кавычек; вам следует правильно использовать printfили убедиться, что вы точно знаете, что передаёте echo.).
tripleee
6

grep -A 10000000 'TERMINATE' файл

  • намного, намного быстрее, чем sed, особенно при работе с действительно большими файлами. Он работает до 10 миллионов строк (или что-то еще, что вы вставляете), так что нет ничего страшного в том, чтобы сделать его достаточно большим, чтобы справиться со всем, что вы ударили.
user8910163
источник
4

Есть много способов сделать это с помощью sedили awk:

sed -n '/TERMINATE/,$p' file

Это ищет TERMINATEв вашем файле и печатает от этой строки до конца файла.

awk '/TERMINATE/,0' file

Это точно такое же поведение, как и sed.

Если вам известен номер строки, с которой вы хотите начать печать, вы можете указать его вместе с NR(номер записи, который в итоге указывает номер строки):

awk 'NR>=535' file

пример

$ seq 10 > a        #generate a file with one number per line, from 1 to 10
$ sed -n '/7/,$p' a
7
8
9
10
$ awk '/7/,0' a
7
8
9
10
$ awk 'NR>=7' a
7
8
9
10
Федорки "ТАК прекратить вредить"
источник
Для номера, который вы также можете использоватьmore +7 file
123
Это включает в себя строку соответствия, которая не является тем, что требуется в этом вопросе.
Mivk
@mivk хорошо, это также случай принятого ответа и 2-го наиболее проголосовавших, так что проблема может быть с вводящим в заблуждение названием.
Федорки 'ТАК прекрати вредить'
3

Если по какой-либо причине вы хотите избежать использования sed, следующий текст напечатает соответствие TERMINATEдо конца файла:

tail -n "+$(grep -n 'TERMINATE' file | head -n 1 | cut -d ":" -f 1)" file

и следующее напечатает от следующей строки соответствия TERMINATEдо конца файла:

tail -n "+$(($(grep -n 'TERMINATE' file | head -n 1 | cut -d ":" -f 1)+1))" file

Чтобы сделать то, что sed может сделать в одном процессе, требуется 2 процесса, и если файл изменяется между выполнением grep и tail, результат может быть непоследовательным, поэтому я рекомендую использовать sed. Кроме того, если файл не содержит TERMINATE, 1-я команда терпит неудачу.

jfg956
источник
файл сканируется дважды. Что делать, если это размер 100 ГБ?
Znik
1
Отказался, потому что это дерьмовое решение, но потом проголосовал, потому что 90% ответов - это предостережения.
Безумный физик
0

Это может быть одним из способов сделать это. Если вы знаете, в какой строке файла у вас есть слово grep и сколько строк в вашем файле:

grep -A466 'TERMINATE' файл

Mariah
источник
1
Если номер строки известен, то grepдаже не требуется; Вы можете просто использовать tail -n $NUM, так что это не совсем ответ.
Самвин
-1

sed - гораздо лучший инструмент для работы: файл sed -n '/ re /, $ p'

где re это регулярное выражение

Другой вариант - флаг grea --after-context. Вам нужно ввести число, чтобы закончить на этом, использование wc в файле должно дать правильное значение для остановки. Объедините это с -n и вашим выражением соответствия.

ckwang
источник
--after-context это хорошо, но не во всех случаях.
Югаль Джиндл
Можете ли вы предложить что-то еще .. ??
Югаль Джиндл
-2

Они будут печатать все строки от последней найденной строки «TERMINATE» до конца файла:

LINE_NUMBER=`grep -o -n TERMINATE $OSCAM_LOG|tail -n 1|sed "s/:/ \\'/g"|awk -F" " '{print $1}'`
tail -n +$LINE_NUMBER $YOUR_FILE_NAME
easyyu
источник
Извлечение номера строки, grepчтобы вы могли набрать его, tailявляется расточительным антипаттерном. Поиск соответствия и печать через конец файла (или, наоборот, печать и остановка при первом совпадении) в высшей степени выполняются с помощью обычных, необходимых самих инструментов регулярных выражений. Массив grep | tail | sed | awkтакже сам по себе является массовым бесполезным использованием grepи друзьями .
tripleee
Я думаю, что он * пытался дать нам что-то, что найдет / последний экземпляр / TERMINATE и даст строки с этого экземпляра. Другие реализации дают вам первый экземпляр вперед. LINE_NUMBER, вероятно, должно выглядеть следующим образом: LINE_NUMBER = $ (grep -o -n 'TERMINATE' $ OSCAM_LOG | tail -n 1 | awk -F: '{print $ 1}') Возможно, не самым элегантным способом, но это Кажется, чтобы сделать работу. ^. ^
fbicknel
... или все в одной строке, но безобразно: tail -n + $ (grep -o -n 'TERMINATE' $ YOUR_FILE_NAME | tail -n 1 | awk -F: '{print $ 1}') $ YOUR_FILE_NAME
fbicknel
.... и я собирался вернуться и отредактировать $ OSCAM_LOG вместо $ YOUR_FILE_NAME ... но не могу по какой-то причине. Не знаю, откуда взялся $ OSCAM_LOG; Я просто бездумно попугай это. oO
fbicknel
Выполнение этого только в Awk - обычная задача в Awk 101. Если вы уже используете более способный инструмент только для того, чтобы получить номер строки, отпустите tailи выполните задачу в более способном инструменте в целом. Во всяком случае, название ясно говорит «первый матч».
tripleee