Хотите заменить только первый случай с помощью sed


27

Исходный файл

claudio
antonio
claudio
michele

Я хочу изменить только первое вхождение "Клаудио" с "Клаудия", чтобы файл результата

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 manpage, выделение мое):

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

С помощью 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

IMK может быть проще с помощью логической переменной состояния:awk '!r && /claudio/ {sub(/claudio/,"claudia"); r=1} 1'
Гленн Джекман

@glennjackman илиawk !x{x=sub(/claudio/,"claudia")}1

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

4

Вот еще 2 программных действия с 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
Да, ты прав. Общая идея та же самая. Но, пожалуйста, попробуйте подставить сингл в двойные кавычки напрямую, и посмотрите, работает ли он. Дьявол кроется в деталях. В этом примере это пробелы и один выход. Я считаю, что это продолжение более ранних ответов может сэкономить кому-то время. И именно поэтому я решил опубликовать пост.
августа

1

Это также можно сделать без пробела удержания и без объединения всех строк в пространство образца:

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

Объяснение: Мы пытаемся найти «claudio», и если мы делаем это, мы прыгаем в маленькую петлю print-load между :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

Sumary

Синтаксис 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.