Sed – Заменить первые k экземпляров слова в файле

Я хочу заменить только первые k экземпляров слова.

Как я могу это сделать?

Например. Скажем, файл foo.txt содержит 100 экземпляров вхождения слова 'linux'.

Мне нужно заменить только первые 50 случаев.

7 Solutions collect form web for “Sed – Заменить первые k экземпляров слова в файле”

В первом разделе описывается использование sed для изменения первых k-вхождений в строке. Второй раздел расширяет этот подход, чтобы изменить только первые k-вхождения в файле, независимо от того, в какой строке они появляются.

Линейное решение

С помощью стандартного sed есть команда для замены k-го появления слова на строке. Если k равно 3, например:

 sed 's/old/new/3' 

Или можно заменить все вхождения:

 sed 's/old/new/g' 

Ни то, ни другое не требуется.

GNU sed предлагает расширение, которое изменит k-ые события и все после этого. Если k равно 3, например:

 sed 's/old/new/g3' 

Их можно комбинировать, чтобы делать то, что вы хотите. Чтобы изменить первые 3 вхождения:

 $ echo old old old old old | sed -E 's/\<old\>/\n/g4; s/\<old\>/new/g; s/\n/old/g' new new new old old 

где \n здесь полезно, потому что мы можем быть уверены, что он никогда не встречается на линии.

Объяснение:

Мы используем три команды замены sed :

  • s/\<old\>/\n/g4

    Это расширение GNU для замены четвертого и всех последующих вхождений old с помощью \n .

    Функция расширенного регулярного выражения \< используется для соответствия началу слова и \> для соответствия концу слова. Это гарантирует соответствие только полных слов. Расширенное регулярное выражение требует опции -E для sed .

  • s/\<old\>/new/g

    Остаются только первые три вхождения old и это заменяет их всех new .

  • s/\n/old/g

    Четвертый и все остальные вхождения old заменялись на \n на первом шаге. Это возвращает их обратно в исходное состояние.

Не-GNU-решение

Если GNU sed недоступен, и вы хотите изменить первые 3 вхождения от old к new , используйте три команды s :

 $ echo old old old old old | sed -E -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/' new new new old old 

Это хорошо работает, когда k – небольшое число, но плохо масштабируется до большого k .

Поскольку некоторые не-GNU seds не поддерживают объединение команд с точкой с запятой, каждая команда здесь вводится с ее собственной опцией -e . Также может потребоваться проверить, поддерживает ли ваш sed символы границы раздела, \< и \> .

Файлоориентированное решение

Мы можем сказать sed прочитать весь файл, а затем выполнить замены. Например, чтобы заменить первые три вхождения old используя sed-стиль BSD:

 sed -E -e 'H;1h;$!d;x' -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/' 

Команда sed H;1h;$!d;x считывает весь файл.

Поскольку вышеупомянутое не использует GNU-расширение, оно должно работать с BSD (OSX) sed. Обратите внимание, что этот подход требует sed который может обрабатывать длинные строки. GNU sed должно быть прекрасным. Те, кто использует не-GNU-версию sed должны проверить свою способность обрабатывать длинные строки.

С помощью GNU sed мы можем использовать описанный выше трюк g , но с заменой \n на \x00 для замены первых трех вхождений:

 sed -E -e 'H;1h;$!d;x; s/\<old\>/\x00/g4; s/\<old\>/new/g; s/\x00/old/g' 

Этот подход хорошо масштабируется, когда k становится большим. Это предполагает, однако, что \x00 не находится в вашей исходной строке. Так как невозможно положить символ \x00 в строку bash, это обычно безопасное предположение.

Предположим, вы хотите заменить только первые три экземпляра строки …

 seq 11 100 311 | sed -e 's/1/\ &/g' \ #s/match string/\nmatch string/globally -e :t \ #define label t -e '/\n/{ x' \ #newlines must match - exchange hold and pattern spaces -e '/.\{3\}/!{' \ #if not 3 characters in hold space do -e 's/$/./' \ #add a new char to hold space -ex \ #exchange hold/pattern spaces again -e 's/\n1/2/' \ #replace first occurring '\n1' string w/ '2' string -e 'bt' \ #branch back to label t -e '};x' \ #end match function; exchange hold/pattern spaces -e '};s/\n//g' #end match function; remove all newline characters 

Примечание: выше, скорее всего, не будет работать со встроенными комментариями
… или в моем примере с примером «1» …

ВЫВОД:

 22 211 211 311 

Там я использую два известных метода. Во-первых, каждое вхождение 1 на строку заменяется на \n1 . Таким образом, как только я делаю рекурсивные замены, я могу быть уверен, что не будет заменять это событие дважды, если моя строка замены содержит мою заменяющую строку. Например, если я заменил he он все равно будет работать.

Я делаю это так:

 s/1/\ &/g 

Во-вторых, я рассчитываю замены, добавляя символ к h старое пространство для каждого вхождения. Как только я дойду до трех, больше не происходит. Если вы примените это к своим данным и измените \{3\} на нужные вам общие замены и /\n1/ address на то, что вы хотите заменить, вы должны заменить только столько, сколько пожелаете.

Я только все делал для чтения. POSIXly Это можно было бы написать так:

 nl=' '; sed "s/1/\\$nl&/g;:t${nl}/\n/{x;/.\{3\}/!{${nl}s/$/./;x;s/\n1/2/;bt$nl};x$nl};s/\n//g" 

И w / GNU sed :

 sed 's/1/\n&/g;:t;/\n/{x;/.\{3\}/!{s/$/./;x;s/\n1/2/;bt};x};s/\n//g' 

Помните также, что sed ориентирован на линию – он не читается во всем файле, а затем пытается обходить его, как это часто бывает в других редакторах. sed является простым и эффективным. Тем не менее, часто бывает удобно сделать что-то вроде следующего:

Вот небольшая функция оболочки, которая объединяет ее в просто исполняемую команду:

 firstn() { sed "s/$2/\ &/g;:t /\n/{x /.\{$(($1))"',\}/!{ s/$/./; x; s/\n'"$2/$3"'/ bt };x };s/\n//g'; } 

Поэтому я могу это сделать:

 seq 11 100 311 | firstn 7 1 5 

…и получить…

 55 555 255 311 

…или…

 seq 10 1 25 | firstn 6 '\(.\)\([1-5]\)' '\15\2' 

…получить…

 10 151 152 153 154 155 16 17 18 19 20 251 22 23 24 25 

… или, чтобы соответствовать вашему примеру (по меньшему порядку величины) :

 yes linux | head -n 10 | firstn 5 linux 'linux is an os kernel' linux is an os kernel linux is an os kernel linux is an os kernel linux is an os kernel linux is an os kernel linux linux linux linux linux 

Использование Awk

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

В приведенных ниже примерах я заменяю первые 27 вхождений old new

Использование sub

 awk '{for(i=1;i<=NF;i++){if(x<27&&$i=="old"){x++;sub("old","new",$i)}}}1' file 

Эта команда проходит через каждое поле до тех пор, пока оно не станет совпадающим со old , оно проверит счетчик ниже 27, увеличивается и заменяет первое совпадение в строке. Затем переместится на следующее поле / строка и повторится.

Замена поля вручную

 awk '{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file 

Как и в предыдущей команде, но поскольку у нее уже есть маркер, в каком поле он равен ($i) , он просто меняет значение поля от old на new .

Выполнение проверки перед

 awk '/old/&&x<27{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file 

Проверяя, что строка содержит старую, а счетчик ниже 27, SHOULD обеспечивать небольшое ускорение скорости, поскольку он не обрабатывает строки, если они являются ложными.

РЕЗУЛЬТАТЫ

Например

 old bold old old old old old nold old old old old old gold old old gold gold old old old old old man old old old old old old dog old old old old old say old old old old old blah old 

в

 new bold new new new new new nold new new new new new gold new new gold gold new new new new new man new new new new new new dog new new new old old say old old old old old blah old 

Простым, но не очень быстрым решением является цикл команд, описанных в https://stackoverflow.com/questions/148451/how-to-use-sed-to-replace-only-the-first-occurrence-in-a -файл

 for i in $(seq 50) ; do sed -i -e "0,/oldword/s//newword/" file.txt ; done 

Эта команда sed, вероятно, работает только для GNU sed, и если новое слово не является частью старого слова . Для не-GNU sed см. Здесь, как заменить только первый шаблон в файле.

Краткая альтернатива Perl:

 perl -pe 'BEGIN{$n=3} 1 while s/old/new/ && ++$i < $n' your_file 

Измените значение `$ n $ по своему вкусу.

Как это работает:

  • Для каждой строки он пытается заменить new для old ( s/old/new/ ) и всякий раз, когда это возможно, он увеличивает переменную $i ( ++$i ).
  • Он продолжает работать над линией ( 1 while ... ) до тех пор, пока она в итоге сделала менее чем $n замен, и может сделать хотя бы одну замену на этой строке.

С помощью GNU awk вы можете установить разделитель записей RS на заменяемое слово, ограниченное границами слов. Тогда это случай установки разделителя записи на выходе на замещающее слово для первых k записей, сохраняя при этом исходный разделитель записей для остатка

 awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \ '{printf "%s%s", $0, NR <= limit? replacement: RT}' file 

ИЛИ

 awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \ '{printf "%s%s", $0, limit--? replacement: RT}' file 

Используйте цикл оболочки и ex !

 { for i in {1..50}; do printf %s\\n '0/old/s//new/'; done; echo x;} | ex file.txt 

Да, это немного глупо.

😉

Примечание. Это может быть неудачно, если в файле осталось менее 50 экземпляров. (Я его не тестировал). Если это так, он оставит файл неизменным.


Еще лучше, используйте Vim.

 vim file.txt qqgg/old<CR>:s/old/new/<CR>q49@q :x 

Объяснение:

 q # Start recording macro q # Into register q gg # Go to start of file /old<CR> # Go to first instance of 'old' :s/old/new/<CR> # Change it to 'new' q # Stop recording 49@q # Replay macro 49 times :x # Save and exit 
  • Согласование многострочного шаблона с помощью sed, awk или grep
  • Как печатать одинаковые строки несколько раз с переменными, измененными с помощью Sed / Awk / anything?
  • Grep / awk / sed для строк, состоящих только из двух букв, и строк, начинающихся с буквы и встречающих определенную длину
  • С помощью sed я хочу заменить первый блок текста, который соответствует
  • Извлеките уникальную строку из каждой строки, содержащей <string>
  • Могут ли команды tr быть закодированы, чтобы избежать нескольких tr процессов в конвейере?
  • Заменить% p на% pK в коде ядра Linux
  • Как заменить символ || с | "" | использование sed
  • Печать определенного раздела строки при наличии значения триггера
  • Как найти и заменить только определенное слово (которое находится в конце файла) с помощью команды оболочки?
  • awk или sed в нижнем регистре / в верхнем регистре только один символ в строке?
  • Linux и Unix - лучшая ОС в мире.