SED заменяет несколько первых вхождений (и диапазонов) шаблона

возможно ли изменить первые 4 (или более) вхождения строки в этом сценарии с помощью SED (напротив sed -r 's/[^[:space:]]*/TEST/4g'):

TEST TEST TEST TEST five six seven

Я получаю работу с обратным порядком слов в строке с использованием AWK дважды, но это долго, сложно, и я хочу получить это только с помощью SED:

echo one two three four five six seven | awk '{for(i=NF;i>=1;i--) printf "%s ", $i;print ""}'  | sed -r 's/[^ ]*/TEST/4g' |  awk '{for(i=NF;i>=1;i--) printf "%s ", $i;print ""}'

Также, возможно, есть возможность изменить диапазоны встречаемости, такие как 3-5, 6-12, ...?

Пример ввода:

один два три четыре пять шесть семь

восемь девять десять одиннадцать двенадцать тринадцать четырнадцать

пятнадцать шестнадцать семнадцать восемнадцать девятнадцать двадцать двадцать один


person mike    schedule 17.06.2019    source источник
comment
awk лучше для этого, вы не поймете загадочную команду sed через шесть месяцев после ее написания.   -  person oguz ismail    schedule 17.06.2019
comment
unix.stackexchange.com/a/155810   -  person Corentin Limier    schedule 17.06.2019
comment
Этот ответ не будет работать, так как здесь искомый текст не является статичным. Однако есть и другие ответы, которые могут подойти здесь.   -  person Wiktor Stribiżew    schedule 17.06.2019
comment
@CorentinLimier Я знаю этот вариант, он будет работать только для одного и того же слова :)   -  person mike    schedule 17.06.2019
comment
Это не отвечает на ваш вопрос, но вы можете упростить свой код, используя rev : echo one two three four five six seven | rev | sed 's/[^ ]*/TSET/4g' | rev . Я пытаюсь найти лучший, так как команду sed необходимо обновить, если строка содержит другое количество слов.   -  person Corentin Limier    schedule 17.06.2019
comment
Пожалуйста, добавьте образец ввода и желаемый результат для этого образца ввода к вашему вопросу.   -  person Cyrus    schedule 17.06.2019
comment
Вроде работает с GNU sed, но нельзя делать диапазоны: sed 's/[^ ][^ ]*/\n&/g;:t;/\n/{x;/.\{4\}/!{s/$/./;x;s/\n[^ ][^ ]*/TEST/;bt};x};s/\n//g' <<< "one two three four five six seven"   -  person Wiktor Stribiżew    schedule 17.06.2019
comment
Простой способ изменить первые четыре строки в строке — добавить маркеры к тем строкам, которые вы хотите заменить, например. sed 's/\S\+/\n&/g;s/\n//5g;s/\n\S\+/TEST/g' file диапазонов в строке можно получить с помощью аналогичного метода.   -  person potong    schedule 18.06.2019
comment
@potong - стоит ответа. Я не видел, как вы получили диапазоны таким образом, но вам просто нужно добавить нижний предел к первой замене. Аккуратный. Это также дает баллы за наличие строки TEST только один раз.   -  person stevesliva    schedule 18.06.2019
comment
@potong приятно :) пока что лучше для меня.   -  person mike    schedule 18.06.2019


Ответы (4)


Как насчет одного AWK:

awk '{for(i=1;i<=NF;i++) if(i<5){$i="TEST"}; print}'

Тестовый забег:

$ echo one two three four five six seven | awk '{for(i=1;i<=NF;i++) if(i<5){$i="TEST"}; print}'
TEST TEST TEST TEST five six seven

Это решение короткое, удобочитаемое и поддерживаемое. Если это вас не устраивает, пожалуйста, добавьте некоторые подробности о вашей конкретной проблеме.


Эквивалентное решение Perl:

perl -pe 's/\S+/$i++<4?"TEST":$&/ge'

Тестовый забег:

$ echo one two three four five six seven | perl -pe 's/\S+/$i++<4?"TEST":$&/ge'
TEST TEST TEST TEST five six seven

возможно, есть возможность изменить диапазоны встречаемости, такие как 3-5, 6-12

АВК:

awk '{for(i=3;i<6;i++)$i="TEST";print}'

Тестовый запуск нового входного файла:

$ awk '{for(i=3;i<6;i++)$i="TEST";print}' input
one two TEST TEST TEST six seven
eight nine TEST TEST TEST thirteen fourteen
fifteen sixteen TEST TEST TEST twenty twenty-one

Перл:

perl -pe 's/\S+/++$c~~[3..5]?"TEST":$&/ge'

Тестовый запуск нового входного файла:

$ perl -pe '$c=0;s/\S+/++$c~~[3..5]?"TEST":$&/ge' input
Smartmatch is experimental at -e line 1. <== This is a warning that goes to STDERR
one two TEST TEST TEST six seven
eight nine TEST TEST TEST thirteen fourteen
fifteen sixteen TEST TEST TEST twenty twenty-one
person simlev    schedule 17.06.2019
comment
Это нормально, но я ищу что-то на основе SED, если это вообще возможно и довольно легко реализовать и запомнить. - person mike; 17.06.2019
comment
@mike Да, вы ясно дали понять, что ищете простое решение только для sed. Мне было интересно, просто ли это ради изучения sed (в этом случае невозможно может быть ответом) или есть некоторые требования, налагаемые рассматриваемой проблемой (в этом случае предоставление немного большего контекста может дать лучшие ответы). - person simlev; 17.06.2019
comment
@mike с sed ничего, кроме s/old/new/, не будет quite easy to implement and remember., вместо этого это будет кошмарный набор рун, который заставит вас хныкать во сне, когда вы встретите его в своем коде 6 месяцев спустя и вам нужно будет его понять. - person Ed Morton; 17.06.2019

Ответ предоставлен здесь пользователем mikeserv. ПРИМЕЧАНИЕ: если вы хотите обработать диапазон, вам нужно использовать максимальную границу, так как будет обработано столько совпадений, сколько возможно, без каких-либо исключений/ошибок.

GNU-сед:

echo 'one two three four five six seven' | \
  sed 's/[^[:space:]]*/\n&/g;:t;/\n/{x;/.\{4\}/!{s/$/./;x;s/\n[^[:space:]]*/TEST/;bt};x};s/\n//g'

POSIX-сед:

nl='
';
echo 'one two three four five six seven' | sed "s/[^[:space:]]*/\\$nl&/g;:t${nl}/\n/{x;/.\{4\}/!{${nl}s/$/./;x;s/\n[^[:space:]]*/TEST/;bt$nl};x$nl};s/\n//g"

См. онлайн-демонстрацию sed.

Оригинальное объяснение (обратите внимание, что здесь 1 заменено на 2, вы можете использовать любые другие шаблоны):

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

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

s/1/\
&/g

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

person Wiktor Stribiżew    schedule 17.06.2019
comment
Да, я думал об этом, но потом перечитал ОП в поисках чего-то не длинного и сложного и отказался от этой идеи. Выбирая между двумя одинаково необъяснимыми требованиями simple и sed, я выбрал первое и отказался от второго. Это, однако, отличное упражнение для изучения sed, это и есть цель. - person simlev; 17.06.2019
comment
Вау, это действительно работает, но очень сложно, я подумал, что есть способ упростить реализацию и понимание этого случая. - person mike; 18.06.2019

Это совершенно неподходящая задача для sed, так как sed предназначен для выполнения простых операций s/old/new/ над отдельными строками, это все. С любым awk в любой оболочке на каждой машине UNIX:

$ echo one two three four five six seven | awk '{for (i=1; i<=4; i++) $i="TEST"}1'
TEST TEST TEST TEST five six seven

$ echo one two three four five six seven | awk '{for (i=3; i<=5; i++) $i="TEST"}1'
one two TEST TEST TEST six seven

и если вам нужно его параметризовать:

echo one two three four five six seven |
    awk -v beg=3 -v end=5 '{for (i=beg; i<=end; i++) $i="TEST"}1'
one two TEST TEST TEST six seven
person Ed Morton    schedule 17.06.2019

$ echo "one two three four fix six" | \
sed -E ':r s/(^|(TEST )+)[^ ]*/\1TEST/;/^(TEST ){4}/!br'
TEST TEST TEST TEST fix six

Объяснение:

  • :r метка с именем r, к которой следует вернуться
  • s/(^|(TEST )+)[^ ]*/\1TEST/; замена, которая заменяет только одно вхождение слова, отличного от TEST, которому предшествует либо начало строки, либо 1 или более TEST
  • /^(TEST ){4}/!br' регулярное выражение для желаемого, за которым следует !br для перехода обратно к :r, если оно еще не совпало.

Понятно, что это хрупко. Это будет бесконечный цикл, если в каких-либо строках нет четырех слов. Может быть только GNU sed.

person stevesliva    schedule 17.06.2019
comment
Что это за персонаж | делает после ^? - person mike; 18.06.2019
comment
В скобках вертикальная черта означает «или». (alice|bob) соответствует любому слову. ^| может выглядеть как два логических оператора, но ^ соответствует началу пространства шаблонов, за которым следует or. - person stevesliva; 18.06.2019