3

Считайте, что у меня есть следующий файл:

echo "1
2
3
4
1
2
3
4
1
2
3
4
1
2
3
4
" > ztest

Здесь я хотел бы изменить только первую 1 на 5 и оставить все остальное без изменений.

Я знаю, что у sed есть команда quit поэтому я пытаюсь сделать следующее:

$ sed 's/1/{5;q}/' ztest 
{5;q}
2
3
4
{5;q}
2
3
4
{5;q}
2
3
4
{5;q}
2
3
4

Это меняет все строки, так что это не хорошо. Итак, я пытаюсь это:

$ sed '{s/1/5/;q}' ztest 
5

Теперь, по-видимому, это выполняется в соответствии с запросом - изменяет первую строку и выходит; Тем не менее, я хотел бы, чтобы остальные строки остались нетронутыми! (поскольку это, в основном, приводит к замене всего файла одним '5')

Так что я в растерянности, какой синтаксис нужен? Обратите внимание, что мне нужно это, чтобы изменить "раннюю" строку во встроенном гигабайтовом файле (используя sed -i); таким образом, я бы хотел, чтобы sed поиск и замену только в первом кратком разделе - и после этого выходные строки не изменялись (поскольку sed -i любом случае сначала выгружает временный файл, а затем перезаписывает оригинал); в надежде сэкономить время обработки.

Заранее спасибо за любые ответы,
Ура!

РЕДАКТИРОВАТЬ: забыл подвопрос: возможно ли распространить это на первые n совпадений? (скажем, в приведенном выше файле первые два '1 заменены на 5 - остальное это дословно?)

5 ответов5

7

Я видел это где-то еще на этом сайте:

sed '/1/{s/1/5/;:a;n;ba}' ztest

Поэтому, как только вы нашли 1-е вхождение, вы зациклились до конца строки чтения и печати файла.

Обновить

Это может быть улучшено, чтобы заменить только N-й матч. Идея заключается в том , чтобы сохранить X в каждом матче holdspace, и когда все X s есть, цикл до конца файла. Ниже приведен скрипт, который заменит 2-е вхождение:

sed '/1/{G;s/\nX\{1\}//;tend;x;s/^/X/;x;P;d};p;d;:end;s/1/5/;:a;n;ba'

Обратите внимание, что вам нужно поставить (N-1) между \{ и \} .

Обновление 2

Я понимаю, что приведенный выше P;d бесполезен, если заменить p;d на P;d . Итак, более простое решение:

sed '/1/{G;s/\nX\{1\}//;tend;x;s/^/X/;x};P;d;:end;s/1/5/;:a;n;ba'
3

Не знаю, выполнимо ли это с помощью sed , но вот версия awk .

awk 'BEGIN {matches=0}
     matches < 2 && /1/ { sub(/1/,"5"); matches++ }
     { print $0 }' ztest 

Это заменит 1 на 5 для первых двух матчей 1 . (Измените 2 там, чтобы увеличить / уменьшить количество совпадений, которые вы хотите.)
После этого файл печатается как есть.

2

Это может работать для вас (GNU sed):

sed '0,/1/s//5/' file
1

Используйте шаблон :a;n;ba еще раз:

$ sed '/1/{s//5/;:A;/1/{s//5/;:B;n;bB};n;bA}'
0

Благодаря первому ответу @ Mat (в StackOverflow, теперь удаленном) синтаксис sed заменяющий только совпадение в первой строке:

sed  '1s/1/5/' ztest

Тем не менее, вы должны четко знать номер строки, где выполнить сопоставление.

Так что, если я знаю, что файл такой же, как выше, то первые две 1 появятся в строках 1 и 5; поэтому для первых двух совпадений я мог бы дать команду sed применить выражение между строками 1 и (скажем) 6 - синтаксис для диапазона строки (адреса) в sed представляет собой запятую (,):

$ sed  '1,6s/1/5/' ztest
5
2
3
4
5
2
3
4
1
2
3
4
1
2
3
4

Аналогично, второе и третье вхождения 1 находятся в строках 5 и 9, поэтому я могу аналогичным образом использовать диапазон, скажем, от строки 3 до строки 10, чтобы изменить второе и третье вхождения 1:

$ sed  '3,10s/1/5/' ztest
1
2
3
4
5
2
3
4
5
2
3
4
1
2
3
4

.... который, я думаю, отвечает на мой вопрос в терминах sed ; хотя я надеялся, что смогу указать количество совпадений напрямую (как в решении awk от @Mat).

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .