Замініть рядок, що містить новий рядок у величезному файлі


16

Хтось знає про нелінійний інструмент "бінарного" пошуку / заміни рядків дещо ефективним способом пам'яті? Дивіться також це питання .

У мене є текстовий файл + 2 Гб, який я хотів би обробити аналогічно тому, як це робиться:

sed -e 's/>\n/>/g'

Це означає, що я хочу видалити всі нові рядки, що з’являються після >, але ніде більше, щоб це виключало tr -d.

Ця команда (що я отримала з відповіді на аналогічне запитання ) не вдається couldn't re-allocate memory:

sed --unbuffered ':a;N;$!ba;s/>\n/>/g'

Отже, чи існують інші методи, не вдаючись до С? Я ненавиджу перл, але я хочу зробити виняток у цьому випадку :-)

Я точно не знаю жодного символу, який не виникає в даних, тому тимчасова заміна \nіншим символом - це те, чого я хотів би уникнути, якщо це можливо.

Будь-які хороші ідеї, будь-хто?


Ви спробували варіант --unbuffered?
ctrl-alt-delor

З --unbufferedпам’яттю або без неї закінчується
MattBianco

Що робить $!?
ctrl-alt-delor

Що не так з першою командою sed. Другий, здається, читає все в просторі шаблонів, я не знаю, що $!це все-таки. Це Я очікую , що буде потрібно БАГАТО пам'яті.
ctrl-alt-delor

Проблема полягає в тому, що sed читає все як рядки, тому перша команда не видаляє нові рядки, оскільки вона виводить текст рядок за рядком. Друга команда - це лише обхідне рішення. Я думаю, що sedце не належний інструмент у цьому випадку.
MattBianco

Відповіді:


14

Це справді банально в Perl, ви не повинні його ненавидіти!

perl -i.bak -pe 's/>\n/>/' file

Пояснення

  • -i: відредагуйте файл на місці та створіть резервну копію оригіналу, що називається file.bak. Якщо ви не хочете робити резервну копію, просто використовуйте perl -i -peзамість цього.
  • -pe: читати вхідний файл рядок за рядком та друкувати кожен рядок після застосування сценарію, заданого як -e.
  • s/>\n/>/: заміна, так само sed.

І ось такий awkпідхід:

awk  '{if(/>$/){printf "%s",$0}else{print}}' file2 

3
+1. awk golf:awk '{ORS=/>$/?"":"\n"}1'
glenn jackman

1
Чому я взагалі не люблю perl - це та сама причина, чому я вибрав цю відповідь (або власне ваш коментар до відповіді Gnouc): читабельність. Використання perl -pe з простим "шаблоном sed" набагато читає, ніж складний sed-вираз.
MattBianco

3
@MattBianco досить справедливо, але, просто так ви знаєте, це не має нічого спільного з Perl. Позиція, яку використовував Gnouc, є особливістю деяких мов регулярного вираження (включаючи, але не обмежуючись ними, PCRE), а не виною Перла. Крім того, після того, як ':a;N;$!ba;s/>\n/>/g'у вашому запитанні було вказано цю чудовисько , ви відмовились від права скаржитися на читаність! : P
тердон

@glennjackman приємно! Я грав з foo ? bar : bazконструктом, але не міг змусити його працювати.
terdon

@terdon: Так, моя помилка. Видаліть його.
cuonglm

7

perlрішення:

$ perl -pe 's/(?<=>)\n//'

Пояснення

  • s/// використовується для підстановки рядків.
  • (?<=>) виглядає за схемою.
  • \n відповідає новому рядку.

Весь шаблон означає, що видаляє всі нові рядки, які є >до нього.


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

2
Навіщо турбуватися з виду за спиною? Чому б не просто s/>\n/>/?
terdon

1
або s/>\K\n//також працювали б
Глен Джекман

@terdon: Тільки перше, що я хоч, видалити замість заміни
cuonglm

@glennjackman: хороший момент!
cuonglm

3

Як щодо цього:

sed ':loop
  />$/ { N
    s/\n//
    b loop
  }' file

Для GNU sed ви також можете спробувати додати параметр -u( --unbuffered) відповідно до питання. GNU sed також задоволений цим як простий однолінійний:

sed ':loop />$/ { N; s/\n//; b loop }' file

Це не видаляє останнє, \nякщо файл закінчується >\n, але це, мабуть, краще все-таки.
Стефан Шазелас

@ StéphaneChazelas, чому закінчення }потрібно мати окремий вираз? це не працюватиме як багаторядковий вираз?
Graeme

1
Це буде працювати в POSIX SEDs з b loop\n}або , -e 'b loop' -e '}'але не так b loop;}і , звичайно , не так , b loop}тому що }і ;дійсні в іменах міток (хоча ніхто при здоровому глузді не буде використовувати. А це означає , що GNU SED НЕ POSIX сумісний) і }потреба команди повинні бути розділене з bкоманди.
Стефан Шазелас

@ StéphaneChazelas, GNU sedзадоволений усім перерахованим вище навіть із --posix! Стандарт також має наступні для дужкових виразів - The list of sed functions shall be surrounded by braces and separated by <newline>s. Чи це не означає, що крапки з комою слід використовувати тільки поза дужками?
Graeme

@mikeserv, цикл потрібен для обробки послідовних рядків, що закінчуються на >. Оригінал ніколи не мав цього, на що вказував Стефан.
Graeme

1

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

sed ':a;$!N;s/>\n/>/;P;D;ba'

EDIT: після перечитування пояснених знаменитих однолінійних підопічних Петріса Крумінса я вважаю, що кращим sedрішенням буде

sed -e :a -e '/>$/N; s/\n//; ta'

який додає наступний рядок у тому випадку, якщо він вже виконав >відповідність у кінці, і повинен умовно циклічно повертатися назад для обробки випадку послідовних відповідних ліній (це Крумін 39. Додайте рядок до наступного, якщо він закінчується зворотним косою рисою «\» саме для заміщення винятком >для \як приєднатися характер, і той факт , що приєднатися символ зберігається на виході).


2
Це не спрацює, якщо закінчуються два рядки поспіль >(це також специфічно для GNU)
Stéphane Chazelas

1

sedне забезпечує способу виведення результатів без остаточного нового рядка. Ваш підхід, що використовує Nпринципово, працює, але зберігає неповні рядки в пам'яті і, таким чином, може вийти з ладу, якщо лінії стануть занадто довгими (реалізація sed зазвичай не розроблена для обробки надзвичайно довгих ліній).

Ви можете використовувати натомість awk.

awk '{if (/<$/) printf "%s", $0; else print}'

Альтернативний підхід полягає у використанні trдля заміни символу нової лінії на "нудний", часто зустрічається характер. Тут може працювати пробіл - виберіть символ, який, як правило, відображається у кожному рядку або принаймні у великій пропорції рядків у ваших даних.

tr ' \n' '\n ' | sed 's/> />/g' | tr '\n ' ' \n'

Обидва методи вже продемонстровані тут для кращого впливу в інших відповідях. І його підхід до sedне працює без 2,5-гігабайтного буфера.
mikeserv

Хтось згадував awk? О, я пропустив це, я лише помітив перл у відповіді Тердона чомусь. Ніхто не згадав про trпідхід - mikeserv, ви опублікували інший (дійсний, але менш загальний) підхід, який трапляється також використовувати tr.
Жил 'ТАК - перестань бути злим'

дійсні, але менш загальні для мене звуки, як ти щойно назвав це робочим, цілеспрямованим рішенням. я думаю, що важко стверджувати, що така річ не корисна, що це не дивно, оскільки вона має 0 нагород. Найбільша різниця, яку я бачу між власним рішенням та вашим більш загальним пропозицією, полягає в тому, що моя спеціально вирішує проблему, тоді як ваша, як правило, може бути вирішена . Це може зробити це вартим - і я навіть можу відмовитись від свого голосу - але тут також є прикрі питання 7 годин між ними та повторювана тема ваших відповідей, що імітують інших. Чи можете ви пояснити це?
mikeserv



-1

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

tr '>\n' '\n>' | sed 's/^>*//;H;/./!d;x;y/\n>/>\n/'

Або навіть:

tr '>\n' '\n>' | sed 's/^>*//' | tr '\n>' '>\n'

Я взагалі не можу отримати вашу першу відповідь про роботу. Хоча я захоплююся елегантністю другої, я вважаю, що вам потрібно зняти *. Так, як зараз, він видалить будь-які порожні рядки за рядком, який закінчується символом a >. … Гм. Озираючись назад на запитання, я бачу, що це трохи неоднозначно. Питання говорить: "Я хочу видалити всі нові рядки, що виникають після >" ... "Я інтерпретую це, щоб означати, що >\n\n\n\n\nfooслід змінити \n\n\n\nfoo, але я думаю, що це fooможе бути бажаний вихід.
Скотт,

@Scott - Я перевірив різні варіанти на наступне: printf '>\n>\n\n>>\n>\n>>>\n>\nf\n\nff\n>\n' | tr '>\n' '\n>' | sed 's/^>*//;H;/./!d;x;y/\n>/>\n/'- це призводить >>>>>>>>>>f\n\nff\n\nдо мене з першою відповіддю. Мені цікаво, що ти робиш, щоб зламати це, бо я хотів би це виправити. Щодо другого пункту - я не згоден, що це неоднозначно. OP не вимагає , щоб видалити всі > попередні на \newline, але замість того, щоб видалити всі \n ewlines наступного> .
mikeserv

1
Так, але правильне тлумачення полягає в тому, що, в >\n\n\n\n\nпершому, новий рядок знаходиться після a >; всі інші дотримуються інших нових рядків. Зауважте, що пропозиція ОП "це те, чого я хочу, якби воно спрацювало" було sed -e 's/>\n/>/g', ні sed -e 's/>\n*/>/g'.
Скотт,

1
@Scott - пропозиція не спрацювала і ніколи не змогла. Я не вірю, що пропозиція коду того, хто не повністю розуміє код, може вважатися дійсною точкою інтерпретації як простою мовою, якою користується також людина. І крім того, результат - якщо він насправді спрацював би - s/>\n/>/на >\n\n\n\n\n, все-таки буде щось, що s/>\n/>/редагуватиметься.
mikeserv
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.