Я хочу замінити лише перші k
екземпляри слова.
Як я можу це зробити?
Напр. Скажімо, файл foo.txt
містить 100 екземплярів слова "linux".
Мені потрібно замінити лише перші 50 випадків.
Я хочу замінити лише перші k
екземпляри слова.
Як я можу це зробити?
Напр. Скажімо, файл foo.txt
містить 100 екземплярів слова "linux".
Мені потрібно замінити лише перші 50 випадків.
Відповіді:
У першому розділі нижче описано використання 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
Це розширення ГНУ замінити четвертий і всі наступні входження old
з \n
.
Функція розширеного регулярного виразів \<
використовується для того, щоб відповідати початку слова та \>
відповідати кінці слова. Це запевняє, що збігаються лише цілі слова. Розширений регулярний вираз вимагає -E
опції для sed
.
s/\<old\>/new/g
old
Залишаються лише перші три випадки, і це замінює їх усіх new
.
s/\n/old/g
Четвертий і всі залишки, що залишилися, old
були замінені \n
на першому кроці. Це повертає їх до початкового стану.
Якщо 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, не підтримують комбінування команд із крапкою з комою, тут кожна команда вводиться із власною -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
в баш-рядок, зазвичай це безпечне припущення.
tr '\n' '|' < input_file | sed …
. Але, звичайно, це перетворює весь вхід в один рядок, і деякі не-GNU набори не можуть обробляти довільно довгі рядки. (2) Ви говорите: "... вище, цитований рядок '|'
слід замінити будь-яким символом або рядком символів, ..." Але ви не можете використовувати tr
для заміни символу рядком (довжиною> 1). (3) Ви говорите в своєму останньому прикладі -e 's/\<old\>/new/' -e 's/\<old\>/w/' | tr '\000' '\n'\>/new
. Це здається помилковим -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/' | tr '\000' '\n'
.
Команди awk можна використовувати для заміни перших N зустрічей слова заміною.
Команди заміняться лише у тому випадку, якщо слово є повним збігом.
У наведених нижче прикладах я замінюю перші 27
подіїold
зnew
Використовуючи під
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
Скажіть, що ви хочете замінити лише перші три екземпляри рядка ...
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
-e x \ #exchange hold/pattern spaces again
-e 's/\n1/2/' \ #replace first occurring '\n1' string w/ '2' string
-e 'b t' \ #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
наhey
нею все одно буде працювати.
Я роблю це так:
s/1/\
&/g
По-друге, я рахую заміни, додаючи символ у h
старий пробіл для кожного події. Як тільки я досягну три більше не трапляються. Якщо ви застосуєте це до своїх даних і зміните \{3\}
на загальну кількість замінників, які ви хочете, і/\n1/
адреси на те, що ви хочете замінити, вам слід замінити лише стільки, скільки ви хочете.
Я робив лише всі -e
речі для читабельності. 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"'/
b t
};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
Коротка альтернатива в 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
заміни загалом, і він може зробити принаймні одну заміну на цій лінії.Використовуйте петлю оболонки і ex
!
{ for i in {1..50}; do printf %s\\n '0/old/s//new/'; done; echo x;} | ex file.txt
Так, це трохи тупо.
;)
Примітка. Це може бути невдалим, якщо old
у файлі менше 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
Просте, але не дуже швидке рішення - переходити до команд, описаних у /programming/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, і якщо newword не є частиною старого слова . Для не-GNU sed дивіться тут, як замінити лише перший шаблон у файлі.
За допомогою 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