Хочете замінити лише перше виникнення sed


26

Оригінальний файл

claudio
antonio
claudio
michele

Я хочу змінити лише перше виникнення "claudio" на "claudia", щоб результат файлу

claudia
antonio
claudio
michele

я намагався

sed -e '1,/claudio/s/claudio/claudia/' nomi

Але виконувати глобальну заміну. ​​Чому?


Подивіться тут linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/…, а також info sed: ( 0,/REGEXP/: Номер рядка 0 може бути використаний у специфікації адреси, наприклад, 0,/REGEXP/щоб sedспробувати відповідати REGEXP і в першому рядку вводу. Іншими словами, 0,/REGEXP/є подібний 1,/REGEXP/, за винятком того, що якщо ADDR2 відповідає першому рядку введення, форма 0, / REGEXP / вважатиме, що він закінчує діапазон, тоді як форма 1, / REGEXP / буде відповідати початку діапазону, а отже, зробить проміжок діапазону до другого виникнення регулярного виразу)
jimmij


awk '/claudio/ && !ok { sub(/claudio/,"claudia"); ok=1 } 1' nomiповинен зробити
Адам Кац

Відповіді:


23

Якщо ви використовуєте GNU sed, спробуйте:

sed -e '0,/claudio/ s/claudio/claudia/' nomi

sedне починає перевіряти регулярний вираз, який закінчує діапазон, поки не буде після рядка, який починає цей діапазон.

З man sed(сторінка POSIX, моє наголос):

Команда редагування з двома адресами повинна вибирати діапазон включення
від першого шаблону , який збігається з першою адресою через
наступний простір шаблону, що відповідає другому. 

Використання awk

Діапазон awkроботи більше, ніж ви очікували:

$ awk 'NR==1,/claudio/{sub(/claudio/, "claudia")} 1' nomi
claudia
antonio
claudio
michele

Пояснення:

  • NR==1,/claudio/

    Це діапазон, який починається з рядка 1 і закінчується першим виникненням claudio.

  • sub(/claudio/, "claudia")

    Поки ми знаходимося в діапазоні, ця команда-заміна виконується.

  • 1

    Це скептична стенограма для awk для друку рядка.


1
Але це передбачає GNU sed.
Стефан Шазелас

@ StéphaneChazelas Він також працює, якщо встановлено POSIXLY_CORRECT, але я думаю, це не означає стільки, як я хотів би. Відповідь оновлена ​​(мені не вистачає для тестових машин BSD).
John1024

IMO може бути простішим із awk '!r && /claudio/ {sub(/claudio/,"claudia"); r=1} 1'
булевою

@glennjackman абоawk !x{x=sub(/claudio/,"claudia")}1

Я також не міг успішно використовувати інший роздільник у першій частині:0,/claudio/
Пат Майрон,

4

Ось ще два програмні зусилля з sed: вони обидва читають весь файл в одну рядок, тоді пошук замінить лише перший.

sed -n ':a;N;$bb;ba;:b;s/\(claudi\)o/\1a/;p' file
sed -n '1h;1!H;${g;s/\(claudi\)o/\1a/;p;}' file

З коментарем:

sed -n '                # don't implicitly print input
  :a                    # label "a"
  N                     # append next line to pattern space
  $bb                   # at the last line, goto "b"
  ba                    # goto "a"
  :b                    # label "b"
  s/\(claudi\)o/\1a/    # replace
  p                     # and print
' file
sed -n '                # don't implicitly print input
  1h                    # put line 1 in the hold space
  1!H                   # for subsequent lines, append to hold space
  ${                    # on the last line
    g                     # put the hold space in pattern space
    s/\(claudi\)o/\1a/    # replace
    p                     # print
  }
' file

3

Нова версія GNU sedпідтримує цю -zопцію.

Зазвичай sed читає рядок, читаючи рядок символів до символу кінця рядка (новий рядок або повернення каретки).
Версія GNU sed додала функцію у версії 4.2.2 для використання символу "NULL". Це може бути корисно, якщо у вас є файли, які використовують NULL як роздільник записів. Деякі утиліти GNU можуть генерувати вихід, який використовує NULL замість нового рядка, наприклад "find. -print0" або "grep -lZ".

Цей параметр можна використовувати, коли ви хочете sedпрацювати над різними лініями.

echo 'claudio
antonio
claudio
michele' | sed -z 's/claudio/claudia/'

повертає

claudia
antonio
claudio
michele

1

Ви можете використовувати awkпрапор, щоб дізнатись, чи була зроблена заміна. Якщо ні, продовжуйте:

$ awk '!f && /claudio/ {$0="claudia"; f=1}1' file
claudia
antonio
claudio
michele

1

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

sed '$H;x;1,/claudio/s/claudio/claudia/;1d' <<\IN
claudio
antonio
claudio
michele
IN

Це просто відкладає перший рядок на другий, а другий на третій і т.д.

Він друкує:

claudia
antonio
claudio
michele

1

І ще один варіант

sed --in-place=*.bak -e "1 h;1! H;\$! d;$ {g;s/claudio/claudia/;}" -- nomi

Перевага полягає в тому, що він використовує подвійну котирування, тому ви можете використовувати змінні всередині, тобто.

export chngFrom=claudio
export chngTo=claudia
sed --in-place=*.bak -e "1 h;1! H;\$! d;$ {g;s/${chngFrom}/${chngTo}/;}" -- nomi

1
Так, ти маєш рацію. Загальна ідея та сама. Але, будь ласка, спробуйте замінити сингл, на подвійні лапки безпосередньо, і подивіться, чи працює він. Диявол лежить у деталях. У цьому прикладі це пробіли та одна втеча. Я вважаю, що це продовження попередніх відповідей може заощадити чийсь час. І саме тому я вирішив опублікувати публікацію.
utom

1

Це також можна зробити без місця утримування та без концентрування всіх рядків у просторі шаблону:

sed -n '/claudio/{s/o/a/;bx};p;b;:x;p;n;bx' nomi

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

sed -n '      # do not print lines by default
  /claudio/ { # on lines that match "claudio" do ...
    s/o/a/    # replace "o" with "a"
    bx        # goto label x
  }           # end of do block
  p           # print the pattern space
  b           # go to the end of the script, continue with next line
  :x          # the label x for goto commands
  p           # print the pattern space
  n           # load the next line in the pattern space (clearing old contents)
  bx          # goto the label x
  ' nomi

1
sed -n '/claudia/{p;Q}'

sed -n '           # don't print input
    /claudia/      # regex search
    {              # when match is found do
    p;             # print line
    Q              # quit sed, don't print last buffered line
    {              # end do block

1
Ви турбувались читати питання?
don_crissti

1

Сумарний

Синтаксис GNU:

sed '/claudio/{s//claudia/;:p;n;bp}' file

Або навіть (використовувати лише один раз слово, яке потрібно замінити:

sed '/\(claudi\)o/{s//\1a/;:p;n;bp}' file

Або в синтаксисі POSIX:

sed -e '/claudio/{s//claudia/;:p' -e 'n;bp' -e '}' file

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

Деталь

Для зміни лише одного рядка потрібно вибрати лише один рядок.

Використовуючи 1,/claudio/(із запитання) вибираєте:

  • з першого рядка (беззастережно)
  • до наступного рядка, що містить рядок claudio.
$ cat file
claudio 1
antonio 2
claudio 3
michele 4

$ sed -n '1,/claudio/{p}' file
claudio 1
antonio 2
claudio 3

Щоб вибрати будь-який рядок, що містить claudio, використовуйте:

$ sed -n `/claudio/{p}` file
claudio 1
claudio 3

І щоб вибрати лише перше claudio у файлі, використовуйте:

sed -n '/claudio/{p;q}' file
claudio 1

Тоді ви можете зробити заміну лише на цьому рядку:

sed '/claudio/{s/claudio/claudia/;q}' file
claudia 1

Що змінить лише перше виникнення збігу регулярних виразів у рядку, навіть якщо їх може бути більше, на першому рядку, що відповідає регексу.

Звичайно, /claudio/регулярний вираз можна спростити до:

$ sed '/claudio/{s//claudia/;q}' file
claudia 1

І, тоді, єдине, чого не вистачає, - це друкувати всі інші рядки без змін:

sed '/claudio/{s//claudia/;:p;n;bp}' file
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.