Замініть рядок, що містить символи нового рядка


10

З bashоболонкою, у файлі з рядками, як наступні

first "line"
<second>line and so on

Я хотів би, щоб замінити один або кілька входжень "line"\n<second>з other charactersі отримати кожен раз , коли :

first other characters line and so on

Тому я повинен замінити рядок як спеціальними символами, такими як, так "і <новим рядком.

Після пошуку між іншими відповідями я виявив, що sedможе приймати нові рядки в правій частині команди (значить, other charactersрядок), але не в лівій.

Чи є спосіб (простіший за це ) отримати цей результат за допомогою sedабо grep?


Ви працюєте з комп'ютером? \nзаяву ewline ви робите чому я питаю. люди рідко запитують, чи можуть вони зробити так, s//\n/як ви можете з GNU sed, хоча більшість інших людей sedвідхилять цю втечу з правого боку. все-таки \nвтеча буде працювати зліва в будь-якому POSIX, sedі ви можете переносити їх так, як, y/c/\n/хоча це матиме такий же ефект, як s/c/\n/gі так завжди, як корисний.
mikeserv

Відповіді:


3

Три різні sedкоманди:

sed '$!N;s/"[^"]*"\n<[^>]*>/other characters /;P;D'

sed -e :n -e '$!N;s/"[^"]*"\n<[^>]*>/other characters /;tn'

sed -e :n -e '$!N;/"$/{$!bn' -e '};s/"[^"]*"\n<[^>]*>/other characters /g'

Усі вони s///базуються на базовій команді ubstitution:

s/"[^"]*"\n<[^>]*>/other characters /

Вони також намагаються подбати про обробку останнього рядка, оскільки вони sedмають тенденцію відрізнятися від результатів у крайових випадках. Це значення $!якого є адресою, що відповідає кожному рядку, який !не є $останнім.

Всі вони також використовують команду Next, щоб додати наступний рядок вводу до простору візерунка, наступного за \nсимволом ewline. Кожен, хто sedпевний час працював, навчиться покладатися на \nперсонаж ewline - адже єдиний спосіб отримати його - це явно помістити його туди.

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

Хоча вони і всі N, всі три відрізняються за методами рекурсії.

Перша команда

Перша команда використовує дуже простий N;P;Dцикл. Ці три команди вбудовані в будь-який POSIX-сумісний sedі вони добре доповнюють одна одну.

  • N- як уже згадувалося, додає Nрядок введення ext до простору візерунка після вставленого \nроздільника ewline.
  • P- подобається p; він Pвказує на простір шаблону, але лише до першого \nперсонажу, що виникає на лінії виходу. І так, враховуючи наступний ввід / команду:

    • printf %s\\n one two | sed '$!N;P;d'
  • sed Prints тільки один . Однак, з ...

  • D- подобається d; він Dвиділяє простір шаблону і починає черговий цикл ліній. На відміну від цього d , Dвидаляє лише до першої \nзустрічної ewline у ​​просторі шаблонів. Якщо в \nсимволі ewline є більше простору шаблону, sedпочинається наступний цикл рядка з того, що залишається. Якщо dв попередньому прикладі були замінені D, наприклад, sedбуде PРінту як один і два .

Ця команда повторюється лише для рядків, які не відповідають s///заяві ubstitution. Оскільки s///ubstitution видаляє \nдоданий ewline N, ніколи нічого не залишається, коли sed Dвибирається простір шаблону.

Тести можна зробити для застосування Pта / або Dвибірково, але є й інші команди, які краще відповідають цій стратегії. Оскільки рекурсія реалізована для обробки послідовних рядків , які відповідають тільки частини правила заміни, послідовні послідовності ліній , відповідних обидва кінці на s///ubstitution не працюють добре.:

Враховуючи цей вхід:

first "line"
<second>"line"
<second>"line"
<second>line and so on

... він друкує ...

first other characters "line"
<second>other characters line and so on

Однак це справляється

first "line"
second "line"
<second>line

...просто добре.

Друге командування

Ця команда дуже схожа на третю. Обидва використовують етикетку :branch / test (що також продемонстровано у відповіді Йозефа Р. тут ) і повторно повертаються до неї за певних умов.

  • -e :n -e- портативні sedскрипти обмежують :визначення мітки або \newline, або новим вбудованим -eоператором xecution.
    • :n- визначає мітку з назвою n. Це можна повернути в будь-який час за допомогою bnабо tn.
  • tn- команда test повертається до вказаної мітки (або, якщо такої немає, виходить із сценарію для поточного циклу рядків), якщо будь-яке s///вставлення, оскільки або мітка була визначена, або з моменту останнього виклику ests tуспішного.

У цій команді відбувається рекурсія відповідних рядків. Якщо sedуспішно замінює візерунок на інші символи , sedповертається до :nмітки та повторює спробу. Якщо s///введення не виконується, sedавторський відбиток-пробіл шаблону і починається наступний цикл рядків.

Це, як правило, краще обробляє послідовні послідовності. Там, де останній не вдався, це друкує:

first other characters other characters other characters line and so on

Третє командування

Як було сказано, логіка тут дуже схожа на останню, але тест є більш явним.

  • /"$/bn- це sedтест. Оскільки команда branch є функцією цієї адреси, вона sedповернеться лише bдо :nтого, як \nдодається ewline, і простір шаблону все ще закінчується "подвійною цитатою.

Між ними Nі bнаскільки це можливо, зроблено якнайменше - таким чином sedможна дуже швидко зібрати рівно стільки, скільки потрібно, щоб переконатися, що наступний рядок не може відповідати вашому правилу. Тут s///ув'язнення відрізняється тим, що в ньому використовується gлобальний прапор - і тому він буде робити всі необхідні заміни одразу. За умови однакового введення ця команда виводить однаково останньому.


Вибачте за тривіальне запитання, але в чому сенс DATAі як ви отримуєте текст?
BowPark

@BowPark - у цьому прикладі зафіксовано <<\DATA\ntext input\nDATA\n, але це лише текст, переданий sedоболонкою у документі тут . Це спрацювало б так, як sed 'script' filenameабо process that writes to stdout | sed 'script'. Чи допомагає це?
mikeserv

Так, це дякую! Чому без Dкожної модифікованої лінії подвійний? (Ви використовували його як потрібно; можливо, я не sedдуже добре знаю )
BowPark

1
@BowPark - ви опускаєте подвоєння при пропущенні, Dтому що в Dіншому випадку Dвиводиться з результатів те, що ви бачите подвоєне. Я щойно зробив правки - і незабаром я можу розширити цю проблему.
mikeserv

1
@BowPark - добре, я оновив його та надав варіанти. Зараз це може бути трохи легше читати / розуміти. Я також явно вирішив Dсправу.
mikeserv

7

Ну, я можу придумати кілька простих способів, але жоден не передбачає grep(що все одно не робить заміни) або sed.

  1. Perl

    Для того, щоб замінити кожне входження "line"\n<second>з other characters, використанням:

    $ perl -00pe 's/"line"\n<second>/other characters /g' file
    first other characters line and so on
    

    Або, щоб трактувати кілька послідовних подій "line"\n<second>як один і замінити їх на один other characters, використовуйте:

    perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    

    Приклад:

    $ cat file
    first "line"
    <second>"line"
    <second>"line"
    <second>line and so on
    $ perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    first other characters line and so on
    

    -00Призводить до Perl , щоб прочитати файл в режимі «пункт» , який означає , що «лінія» визначається шляхом \n\nзамість \n, по суті, кожен пункт розглядаються як лінія. Тому заміна відповідає новому рядку.

  2. awk

    $  awk -v RS="\n\n" -v ORS="" '{
          sub(/"line"\n<second>/,"other characters ", $0)
          print;
        }' file 
    first other characters line and so on
    

    Ця ж основна ідея, ми встановлюємо розділювач записів ( RS), щоб \n\nзрізати весь файл, потім роздільник запису виводу ні до чого (інакше надрукується додатковий новий рядок), а потім використовуємо sub()функцію для заміни.


2
@mikeserv? Який? Друге передбачається, що ОП заявило, що вони хочуть "замінити одну або кілька випадків", тому споживання пункту може бути тим, що вони очікують.
terdon

дуже хороший момент. Я думаю, що я зосереджувався більше і отримував щоразу , але, мабуть, незрозуміло, чи це повинна бути одна заміна за подією або одна заміна за послідовністю подій ... @BowPark?
mikeserv

Потрібна одна заміна на подію.
BowPark

@BowPark ОК, тоді і перший підхід Perl, або awk повинні працювати. Чи не дають вони бажаного результату?
тердон

Це працює, спасибі, але третій рядок з awkповинен бути print;}' file. Мені потрібно уникати Perl і бажано використовувати sed, все одно ви запропонували хороші альтернативи.
BowPark

6

прочитати весь файл і зробити глобальну заміну:

sed -n 'H; ${x; s/"line"\n<second>/other characters /g; p}' <<END
first "line"
<second> line followed by "line"
<second> and last
END
first other characters  line followed by other characters  and last

Так. Це працює, але що робити, якщо у мене кілька випадків?
BowPark

Так, правильно. Виправлено
glenn jackman

1
Вибачте, нітко виберіть ще раз, але ${cmds}специфічно для GNU - для більшості інших sedзнадобиться \newline або -eперерва між pі }. Ви можете уникнути дужок в цілому - і портативно - і навіть уникнути вставлення додаткового \nсимволу ewline на перший рядок, наприклад:sed 'H;1h;$!d;x;s/"line"\n<second>/other characters /g'
mikeserv

Я перевірив це, і здається, не портативний. Він друкує додатковий новий рядок на початку виводу, але результат правильний у GNU.
BowPark

Щоб видалити провідний новий рядок: sed -n '1{h;n};H; ${x; s/"line"\n<second>/other characters /g; p}'- однак це стає неможливим.
Гленн Джекман

3

Ось варіант відповіді glenn, який спрацює, якщо у вас є кілька послідовних випадків (працює sedлише з GNU ):

sed ':x /"line"/N;s/"line"\n<second>/other characters/;/"line"/bx' your_file

Це :xлише мітка для розгалуження. В основному, це робить те, що він перевіряє рядок після заміни, і якщо вона все-таки збігається "line", вона відгалужується до :xмітки (саме bxце і робиться) і додає ще один рядок до буфера і починає його обробляти.


@mikeserv Будь ласка, будьте конкретні про те, що ви маєте на увазі. Це працювало для мене.
Джозеф Р.

@mikeserv Вибачте, я дійсно не знаю, про що ви говорите. Я скопіював вищезазначений рядок коду в свій термінал, і він працював правильно.
Джозеф Р.

1
втягнуто - це, мабуть, працює в GNU, sedякий займає обробку етикетки, що не є POSIX, досить далеко, щоб прийняти пробіл як роздільник для декларації етикетки. Слід зазначити, що будь-який інший sedзазнає невдачі - і не зможе N. GNU sedпорушує вказівки POSIX для друку простору шаблону перед тим, як вийти з Nостаннього рядка, але POSIX дає зрозуміти, що якщо Nкоманда читається в останньому рядку, нічого не слід друкувати.
mikeserv

Якщо ви редагуєте публікацію, щоб вказати GNU, я скасую свій голос і видаляю ці коментарі. Крім того, можливо, варто дізнатися про vкоманду GNU, яка перерветься у всіх інших, sedале є неоперативною у версіях GNU 4 і вище.
mikeserv

1
в цьому випадку я буду пропонувати один більше - це може бути зроблено переносимо , як: sed -e :x -e '/"line"/{$!N' -e '};s/"line"\n<second>/other characters/;/"line"/bx'.
mikeserv
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.