Як замінити лише N-е виникнення шаблону у файлі?


10

Як замінити третій рядок у файлі за допомогою sedкоманди.

Приклад:

Зміна лише третє входження isв usв файлі.

Мій вхідний файл містить:

hai this is linux.
hai this is unix.
hai this is mac.
hai this is unchanged.

Я очікую, що вихід:

hai this is linux.
hai thus is unix.
hai this is mac.
hai this is unchanged.

3
Вхід і вихід однакові.
Hauke ​​Laging

4
sedне є правильним інструментом для роботи.
choroba

@don_crissti Я це виправив. ОП не використовувало інструментів форматування (до речі, Сурешкумар, див. Тут допомогу щодо редагування ваших запитань), а наступні редактори неправильно зрозуміли, що потрібно.
terdon

Відповіді:


11

Це зробити набагато простіше perl.

Для зміни 3- го появи:

perl -pe 's{is}{++$n == 3 ? "us" : $&}ge'

Щоб змінити кожне 3 виникнення:

perl -pe 's{is}{++$n % 3 ? $& : "us"}ge'

3

Коли рядок заміни відбувається лише один раз у рядку, ви можете комбінувати різні утиліти.
Коли вхід знаходиться у файлі "input" і ви замінюєте "is" на "us", ви можете використовувати

LINENR=$(cat input | grep -n " is " | head -3 | tail -1 | cut -d: -f1)
cat input | sed ${LINENR}' s/ is / us /'

У прикладі у запитанні є декілька isна кожен рядок.
terdon

Я думав, ти шукаєш "є" з пробілами. Я міг би відредагувати свою відповідь командою tr на зразок @jimmij, яка використовується, але моє рішення стане набагато погірше його.
Вальтер А

Я не запитувач :). Я думав , що те ж саме, тому я upvoted своєї відповіді, але якщо ви подивитеся на оригінальну версію питання (натисніть на кнопку «Edited X хвилин назад» посилання) ви побачите , що OP очікується це в цьому бути змінені таким чином . До речі, в коті немає потреби .
terdon

2

Сценарій нижче (використовуючи синтаксис GNU sed ) може бути використаний для редагування на місці, а не для виводу, оскільки він зупиняє рядки друку після потрібної заміни:

sed -i '/is/{: 1 ; /\(.*is\)\{3\}/!{N;b1} ; s/is/us/3 ; q}' text.file

Якщо вам подобається рішення choroba, ви можете змінити вище

sed '/is/{:1 ; /\(.*is\)\{3\}/!{N;b1} ; s/is/us/3 ; :2 ; n ; $!b2}' text.file

який виводить усі рядки

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

sed ': 1 ; N ; $!b1 ; s/is/us/3 ' text.file

2

Ви можете використовувати sedдля цього, якщо раніше нові рядки замінюються будь-якими іншими символами, наприклад:

tr '\n' '\000' | sed 's/is/us/3' | tr '\000' '\n'

І те саме з чистим (GNU) sed:

sed ':a;N;$!ba;s/\n/\x0/g;s/is/us/3;s/\x0/\n/g'

( sedЗаміна нового рядка безсоромно вкрадена з https://stackoverflow.com/a/1252191/4488514 )


Якщо ви збираєтесь використовувати sedспецифічний синтаксис GNU , ви можете також використовувати sed -z 's/is/us/3'.
Стефан Шазелас

@ StéphaneChazelas -zповинна бути якоюсь новою функцією, я GNU sed version 4.2.1нічого не знаю про цей варіант.
jimmij

1
Додано в 4.2.2 (2012). У вашому другому рішенні вам не потрібно конверсію \x0.
Стефан Шазелас

Вибачте за редагування. Я не бачив оригінальної версії запитання, і хтось його неправильно зрозумів і відредагував неправильну лінію. Я повернувся до попередньої версії.
terdon

1
p='[:punct:]' s='[:space:]'
sed -Ee'1!{/\n/!b' -e\}            \
     -e's/(\n*)(.*)/ \2 \1/'       \
     -e"s/is[$p]?[$s]/\n&/g"       \
     -e"s/([^$s])\n/\1/g;1G"       \
-e:c -e"s/\ni(.* )\n{3}/u\1/"      \
     -e"/\n$/!s/\n//g;/\ni/G"      \
     -e's//i/;//tc'                \
     -e's/^ (.*) /\1/;P;$d;N;D'

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

Підсумок полягає в тому, що він модифікує лише третій випадок у файлі - і він проводитиме рахунки за рядок. Отже, якщо файл виглядає так:

1. is is isis
2. is does

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

1. is is isis
2. us does

Спочатку він обробляє крайові корпуси, вставляючи пробіл на голову та хвіст кожного рядка. Це полегшує встановлення меж слова.

Далі він шукає дійсні ises, вставляючи \newline перед усіма подіями, isякі негайно передують нулю або одному знаку пунктуації, після якого пробіл. Він робить ще один прохід і видаляє всі \nлінії виходу, яким безпосередньо передує символ, який не знаходиться в космосі. Це залишило позаду маркерів буде відповідати is.і , isале не thisабо ?is.

Далі він збирає кожен маркер до хвоста струни - для кожного \niматчу в рядку додає \nевлайн до хвоста струни і замінює його на iабо u. Якщо є три \nлінії в рядку, зібрані в кінці рядка, тоді він використовує u - інакше i. Перший раз, коли використовується au, також є останнім - заміна заповнює нескінченний цикл, який зводиться до get line, print line, get line, print line,тощо.

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

Я додаю lкоманду ook на чолі циклу, наприклад:

l; s/\ni(.* )\n{9}/u\1/...

... і подивіться, що це робить, як це працює з цим входом:

hai this is linux.
hai this is unix.


hai this is mac.
hai this is unchanged is.

... так ось що це робить:

 hai this \nis linux. \n$        #behind the scenes
hai this is linux.               #actually printed
 hai this \nis unix. \n\n$       #it builds the marker string
hai this is unix.
  \n\n\n$                        #only for lines matching the

  \n\n\n$                        #pattern - and not otherwise.

 hai this \nis mac. \n\n\n$      #here's the match - 3 ises so far in file.
hai this us mac.                 #printed
hai this is unchanged is.        #no look here - this line is never evaled

Має більше сенсу, можливо, з більшою кількістю isес на рядок:

nthword()(  p='[:punct:]' s='[:space:]'         
    sed -e '1!{/\n/!b' -e\}             \
        -e 's/\(\n*\)\(.*\)/ \2 \1/'    \
        -e "s/$1[$p]\{0,1\}[$s]/\n&/g"  \
        -e "s/\([^$s]\)\n/\1/g;1G;:c"   \
        -e "${dbg+l;}s/\n$1\(.* \)\n\{$3\}/$2\1/" \
        -e '/\n$/!s/\n//g;/\n'"$1/G"    \
        -e "s//$1/;//tc" -e 's/^ \(.*\) /\1/'     \
        -e 'P;$d;N;D'
)        

Це практично те саме, але написане w / POSIX BRE та рудиментарна обробка аргументів.

 printf 'is is. is? this is%.0s\n' {1..4}  | nthword is us 12

... стає ...

is is. is? this is
is is. is? this is
is is. is? this us
is is. is? this is

... і якщо я ввімкну ${dbg}:

printf 'is is. is? this is%.0s\n' {1..4}  | 
dbg=1 nthword is us 12

... ми можемо спостерігати, як це повторюється ...

 \nis \nis. \nis? this \nis \n$
 is \nis. \nis? this \nis \n\n$
 is is. \nis? this \nis \n\n\n$
 is is. is? this \nis \n\n\n\n$
is is. is? this is
 \nis \nis. \nis? this \nis \n\n\n\n\n$
 is \nis. \nis? this \nis \n\n\n\n\n\n$
 is is. \nis? this \nis \n\n\n\n\n\n\n$
 is is. is? this \nis \n\n\n\n\n\n\n\n$
is is. is? this is
 \nis \nis. \nis? this \nis \n\n\n\n\n\n\n\n\n$
 is \nis. \nis? this \nis \n\n\n\n\n\n\n\n\n\n$
 is is. \nis? this \nis \n\n\n\n\n\n\n\n\n\n\n$
 is is. is? this \nis \n\n\n\n\n\n\n\n\n\n\n\n$
is is. is? this us
is is. is? this is

Ви зрозуміли, що ваш приклад каже "isis"?
flarn2006

@ flarn2006 - я майже впевнений, що це так і є.
mikeserv

0

Ось логічне рішення, яке використовує sedі trмає бути записане в сценарії, щоб воно працювало. Код нижче замінює кожен 3-й зустріч слова, зазначеного в sedкоманді. Замініть i=3на, i=nщоб зробити цю роботу для будь-якої n.

Код:

# replace new lines with '^' character to get everything onto a single line
tr '\n' '^' < input.txt > output.txt

# count number of occurrences of the word to be replaced
num=`grep -o "apple" "output.txt" | wc -l`

# in successive iterations, replace the i + (n-1)th occurrence
n=3
i=3
while [ $i -le $num ]
do
    sed -i '' "s/apple/lemon/${i}" 'output.txt'
    i=$(( i + (n-1) ))
done

# replace the '^' back to new line character
tr '^' '\n' < output.txt > tmp && mv tmp output.txt


Чому це працює:

Припустимо, текстовий файл є a b b b b a c a d a b b b a b e b z b s b a b.

  • Коли n = 2: ми хочемо замінити кожен другий випадок b.

    • a b b b b a c a d a b b b a b e b z b s b a b
      . . ^ . ^ . . . . . . ^ . . ^ . . . ^ . ^ . ^
    • Спочатку замінюємо 2-е входження, потім 3-е входження, потім 4-е, 5-е і так далі. Порахуйте у наведеній вище послідовності, щоб побачити це на собі.
  • Коли n = 3: ми хочемо замінити кожне третє виникнення b.

    • a b b b b a c a d a b b b a b e b z b s b a b
      . . . ^ . . . . . . . ^ . . . . ^ . . . . . ^
    • Спочатку замінюємо 3-е входження, потім 5-е, потім 7-е, 9-е, 11-е і так далі.
  • Коли n = 4: ми хочемо замінити кожне третє виникнення b.

    • Спочатку ми замінюємо 4-е виникнення, потім 7-е, потім 10-е, 13-е і так далі.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.