Як отримати частину файлу після першого рядка, який відповідає регулярному виразу?


169

У мене файл з приблизно 1000 рядків. Я хочу, щоб частина мого файлу була після рядка, який відповідає моїй заяві grep.

Це є:

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

Отже, я хочу, щоб файл з рядка 535 до рядка 1000 для подальшої обробки.

Як я можу це зробити?


34
UUOC (Безкорисне використання кота):grep 'TERMINATE' file
Яків

30
Я знаю це, як його я використовую саме так. Давайте повернемося до питання.
Yugal Jindle

3
Це ідеальне питання програмування і добре підходить для stackoverflow.
aioobe

13
@Jacob Це зовсім не марне використання кота. Його використання полягає в друкуванні файлу на стандартний вихід, а це означає, що ми можемо використовувати grepстандартний інтерфейс введення для читання даних, а не вивчати, до якого комутатора слід застосувати grep, і sed, і awk, і pandoc, і ffmpegт.д., коли ми хочемо читати з файлу. Це економить час, оскільки нам не доведеться вчитися новому комутатору щоразу, коли ми хочемо зробити те саме: читати з файлу.
рунекс

@runeks Я згоден з вашими настроями - але ви можете досягти цього без кота : grep 'TERMINATE' < file. Можливо, це робить читання трохи складніше - але це сценарій оболонки, тож це завжди буде проблемою :)
ЗАРАЗ

Відповіді:


307

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

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

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

Це буде надруковано з рядка, який слід за збігом рядків TERMINATEдо кінця файлу:
(ПІСЛЯ відповідного рядка до EOF, НЕ включаючи відповідний рядок)

sed -e '1,/TERMINATE/d'

Пояснюється: 1,/TERMINATE/ це вибір діапазону адреси (рядка), що означає перший рядок для введення в 1-й рядок, що відповідає TERMINATEзвичайному виразу, і dє командою delete, яка видаляє поточний рядок і переходить до наступного рядка. Оскільки sedповедінка за замовчуванням полягає у друкуванні рядків, вони надрукують рядки після TERMINATE закінчення введення.

Редагувати:

Якщо ви хочете, щоб рядки були раніше TERMINATE:

sed -e '/TERMINATE/,$d'

І якщо ви хочете, щоб обидва рядки до і після були TERMINATEу двох різних файлах за один прохід:

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

Файли до і після містять рядок із завершенням, тому для обробки кожного, що вам потрібно використовувати:

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.

Як ми можемо отримати рядки перед TERMINATE та видалити все, що випливає?
Yugal Jindle

Як би замінив твердо кодований термін термінал змінною?
Sébastien Clément

2
Один випадок використання, якого тут немає, - це друк рядків після останнього маркера (якщо їх у файлі може бути декілька. Подумайте, файли журналів тощо).
мато

Приклад sed -e "1,/$matchtext/d"не працює, коли $matchtextвідбувається в першому рядку. Мені довелося це змінити sed -e "0,/$matchtext/d".
Каральга

61

Як просте наближення ви можете використовувати

grep -A100000 TERMINATE file

яка знімає TERMINATEта виводить до 100000 рядків за цим рядком.

Зі сторінки людини

-A NUM, --after-context=NUM

Роздрукуйте NUM рядків контуру контуру після відповідних рядків. Розміщує рядок, що містить роздільник групи (-) між суміжними групами сірників. Якщо параметр -o або --only-match, це не має ефекту, і надається попередження.


Це може спрацювати для цього, але мені потрібно зашифрувати це у своєму скрипті, щоб обробити багато файлів. Отже, покажіть якесь загальне рішення.
Югал Джіндл

3
Я думаю, що це одне практичне рішення!
michelgotta

2
аналогічно -B NUM, --bere-context = NUM ​​Друк NUM рядків провідного контексту перед узгодженням рядків. Розміщує рядок, що містить роздільник групи (-) між суміжними групами сірників. Якщо параметр -o або --only-match, це не має ефекту, і надається попередження.
PiyusG

це рішення працювало для мене, тому що я можу легко використовувати змінні як мою рядок для перевірки.
Хосе Мартінес

3
Хороша ідея! Якщо ви не впевнені в розмірі контексту, можете fileзамість них порахувати рядки :grep -A$(cat file | wc -l) TERMINATE file
Lemming

26

Інструмент для використання тут awk:

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

Як це працює:

  1. Встановлюємо змінну "знайдено" в нуль, оцінюючи хибну
  2. якщо відповідність терміна "TERMINATE" буде знайдена з регулярним виразом, встановимо його на один.
  3. Якщо наша "знайдена" змінна оцінюється як 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 або порожній рядок, якщо він не визначений. Це дійсно, але може бути не найкращою практикою, тому ви можете додати a BEGIN{found=0}на початок екс-виразу.
  • Якщо знайдено кілька стартових кінцевих блоків, всі вони надрукуються.

1
Дивовижний приклад. Щойно провів 2 години, переглядаючи csplit, sed та всілякі складні команди awk. Це не тільки зробив те, що я хотів, але показав досить просто, щоб зробити висновок, як його змінити, щоб зробити кілька інших пов'язаних мені речей. Змушує мене запам’ятати, що awk - це чудово, а не тільки в нерозбірливій безладді Дякую.
користувач1169420

{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, вони обидва повинні робити те ж саме.
користувач000001

7

Використовуйте розширення параметра bash таким чином:

content=$(cat file)
echo "${content#*TERMINATE}"

Чи можете ви пояснити, що ви робите?
Yugal Jindle

Я скопіював вміст "файла" у змінну $ content. Потім я видалив усіх персонажів, поки не було видно "ЗАКРИТИ". Він не використовував жадної відповідності, але ви можете використовувати жадібну відповідність за $ {content ## * TERMINATE}.
Му Його

ось посилання на посібник з bash: gnu.org/software/bash/manual/…
Mu Qiao

6
що буде, якщо файл розміром 100 Гб?
Znik

1
Downvote: Це жахливо (читання файлу в змінну) і неправильне (використовуючи змінну, не цитуючи її; і вам слід правильно використовувати printfабо переконайтесь, що ви точно знаєте, до чого переходите echo.).
tripleee

6

grep -A 10000000 файл "ЗАКРИТИ"

  • набагато, набагато швидше, ніж sed, особливо працює над дійсно великим файлом. Він працює до 10 мільйонів ліній (або все, що ви вкладаєте), тому не зашкодьте зробити це досить великим, щоб вирішити будь-що, що потрапило.

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 добре, це теж випадок прийнятої відповіді та другий найбільш схвальний, тому проблема може бути з оманливим заголовком.
fedorqui 'ТАК перестаньте шкодити'

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, в одному процесі потрібно два процеси, і якщо файл зміниться між виконанням grep і хвостом, результат може бути невідповідним, тому я рекомендую використовувати sed. Більше того, якщо файли не містять TERMINATE, перша команда не працює.


файл сканується двічі. що робити, якщо він розміром 100 Гб?
Znik

1
Незважаючи на те, що це шалене рішення, але потім відхилено, оскільки 90% відповідей - застереження.
Божевільний фізик


0

Це може бути одним із способів зробити це. Якщо ви знаєте, у якому рядку файлу є ваше слово grep і скільки рядків у вашому файлі:

grep -A466 файл "ЗАКРИТИ"


1
Якщо номер рядка відомий, він grepнавіть не потрібен; ви можете просто використовувати tail -n $NUM, так що це насправді не відповідь.
Самвін

-1

sed - набагато кращий інструмент для роботи: файл sed -n '/ re /, $ p'

де re - регексп.

Інший варіант - греп - прапор після контексту. Вам потрібно вказати число, яке закінчиться на, використання wc у файлі повинно дати потрібне значення, на якому зупиниться. Поєднайте це з виразом -n та відповідності.


- після цього контекст прекрасний, але не у всіх випадках.
Югал Джіндл

Ви можете запропонувати щось інше .. ??
Yugal Jindle

-2

Вони надрукують усі рядки з останнього знайденого рядка "ЗАКРИТИ" до кінця файлу:

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

Вилучення номера рядка, grepщоб ви могли його подати, tail- це марний антипатерн. Пошук відповідності та друк до кінця файлу (або, навпаки, друк та зупинка при першій відповідності) виконуються за допомогою самих звичайних, необхідних інструментів регулярного виведення. Масове grep | tail | sed | awkтакож саме по собі є масовим марним використанням grepта друзями .
трійка

Я думаю, що s * він намагався дати нам щось, що знайде / останній екземпляр / терміна "TERMINATE" і надасть рядки від цього примірника. Інші реалізації дають вам перший екземпляр далі. LINE_NUMBER, мабуть, повинен виглядати приблизно так: LINE_NUMBER = $ (grep -o -n 'TERMINATE' $ OSCAM_LOG | хвіст -n 1 | awk -F: '{print $ 1}') Можливо, не найвишуканіший спосіб, але це схоже, виконає роботу. ^. ^
fbicknel

... або все в одному рядку, але некрасиво: хвіст -n + $ (grep -o -n 'TERMINATE' $ YOUR_FILE_NAME | хвіст -n 1 | awk -F: '{print $ 1}') $ YOUR_FILE_NAME
fbicknel

.... і я збирався повернутися назад і відредагувати $ OSCAM_LOG замість $ YOUR_FILE_NAME ... але я не можу чомусь. Не знаю, звідки береться $ OSCAM_LOG; Я просто бездумно її папугував. оо
fbicknel

Виконання цього лише в Awk - це поширене завдання в Awk 101. Якщо ви вже використовуєте більш спроможний інструмент, щоб отримати номер рядка, відпустіть його tailта виконайте завдання в більш спроможному інструменті взагалі. У будь-якому випадку, заголовок чітко говорить про "перший матч".
трійчатка
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.