sed -f не выполняется несколько одинаковых команд сопоставления с образцом, включая сцепление, для входного файла с несколькими строками? - PullRequest
2 голосов
/ 11 марта 2020

У меня есть куча команд sed в командном файле, который я запускаю, используя -f.

/PATTERN1 /I,/;/s/^[ \t]*//g
/PATTERN1 /I{:a;/;/!N;s/\n/ /;ta;P;D}
s/\(PATTERN1\) \([ \tA-Za-z0-9,\"\']*\)(\(.*\))[ \t]*;[ \t]*$/\1 \2\3;/I

Если я запускаю

gsed -f sed-file.sed input-file 

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

Мой пример файла входного файла

    not (this line);
pattern1 some text, ( some other text (5), some other text (6));
pattern1 this text
(
     that text (6),
     that text (7),
);
not this text either;

Мне бы хотелось, чтобы это выглядело так

    not (this line);
pattern1 some text,  some other text (5), some other text (6);
pattern1 this text that text (6), that text (7), ;
not this text either;

Итак, если я оставлю все строки в файле sed без комментариев (как указано выше), то получу:

    not (this line);
pattern1 some text, ( some other text (5), some other text (6));
pattern1 this text (      that text (6),      that text (7), );
not this text either;

Если я закомментирую первые 2 строки, я получу

    not (this line);
pattern1 some text,  some other text (5), some other text (6);
pattern1 this text
(
     that text (6),
     that text (7),
);
not this text either;

Где в первой строке с pattern1 правильно удалены окружающие скобки.

Если я закомментирую только первую строку, я получу

    not (this line);
pattern1 some text, ( some other text (5), some other text (6));
pattern1 this text (      that text (6),      that text (7), );
not this text either;

Где строки, совпадающие с pattern1, объединяются вплоть до точки с запятой, но окружающие скобки больше не удаляются.

И если я закомментирую последнюю строку, я получаю то же самое, но пробелы не удаляются ...

    not (this line);
pattern1 some text, ( some other text (5), some other text (6));
pattern1 this text (      that text (6),      that text (7), );
not this text either;

И если я закомментирую последние 2 строки, я получаю:

    not (this line);
pattern1 some text, ( some other text (5), some other text (6));
pattern1 this text
(
that text (6),
that text (7),
);
not this text either;

Где пробелы правильно удаляются в начале строк с pattern1 и заканчивая точкой с запятой.

Как я могу убедиться, что все 3 seds обработаны по порядку, но использовать одну команду? Или мне придется управлять ими отдельно?

Ответы [ 3 ]

1 голос
/ 11 марта 2020

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

SED PROGRAM:
  /PATTERN1 /I,/;/ s/^[ \t]*//g
  /PATTERN1 /I {
    :a
    /;/! N
    s/\n/ /
    t a
    P
    D
  }
  s/\\(PATTERN1\\) \\([ \tA-Za-z0-9,\\"\\']*\\)(\\(.*\\))[ \t]*;[ \t]*$/\1 \2\3;/i

<snip>

INPUT:   'infile' line 2
PATTERN: pattern1 some text, ( some other text (5), some other text (6));
COMMAND: /PATTERN1 /I,/;/ s/^[ \t]*//g
MATCHED REGEX REGISTERS
  regex[0] = 0-0 ''

<snip>

PATTERN: pattern1 some text, ( some other text (5), some other text (6));
COMMAND:   t a
COMMAND:   P
pattern1 some text, ( some other text (5), some other text (6));
COMMAND:   D
INPUT:   'infile' line 3
PATTERN: pattern1 this text

Обратите внимание, как после D следующая строка загружается в буфер шаблонов и ваша третья команда никогда не выполняется из-за этого. В руководстве говорится об этом D (выделено мной):

D
Если пространство шаблона не содержит новой строки, начните обычный новый цикл как если бы была введена команда d. В противном случае удалите текст в пространстве шаблона до первой новой строки и перезапустите цикл с результирующим пространством шаблона, не читая новую строку ввода.

На этом этапе пространство шаблона никогда не содержит символ новой строки, и вы просто начинаете новый цикл.

Похоже, ваш скрипт можно исправить следующим образом:

/PATTERN1 /I,/;/ s/^[ \t]*//g
/PATTERN1 /I {
    :a
    /;/! N
    s/\n/ /
    t a
    s/[[:blank:]]\{1,\}/ /g
}

Вам не нужен шаблон P;D; это обычно используется, когда вы хотите перемещение окна из нескольких строк. Вместо вашей третьей команды я добавил подстановку после l oop во второй команде.

1 голос
/ 11 марта 2020

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

$ cat tst.awk
tolower($0) ~ tolower("^pattern1") { inBlock = 1 }
inBlock {
    block = block $0 ORS
    if ( sub(/);\n/,";",block) ) {
        sub(/\(/,"",block)
        gsub(/[[:space:]]+/," ",block)
        print block
        block = ""
        inBlock = 0
    }
    next
}
{ print }

$ awk -f tst.awk file
    not (this line);
pattern1 some text, some other text (5), some other text (6);
pattern1 this text that text (6), that text (7), ;
not this text either;

. Он просто ищет строку, начинающуюся с "pattern1", и когда находит, создает оттуда блок текста до первого );, который он находит в конце строки, затем удаляет первый ( и последний ), преобразует все цепочки пробелов в один пробел и печатает блок. Никаких тайн, crypti c, односимвольных рун не требуется, только простая и понятная программа, которая будет работать на любом блоке UNIX и которую легко усовершенствовать в будущем, если / когда вам понадобится что-то еще.

Если вы не возражаете против использования решения c, специфицированного GNU, вот более простое решение с GNU awk, которое просто полагается на завершение каждой записи с помощью ;\n:

$ cat tst.awk
BEGIN {
    RS=ORS=";\n"
    IGNORECASE=1
}
/^pattern1/ {
    $0 = gensub(/\((.*)\)/,"\\1",1)
    gsub(/[[:space:]]+/," ")
}
{ print }

$ awk -f tst.awk file
    not (this line);
pattern1 some text, some other text (5), some other text (6);
pattern1 this text that text (6), that text (7), ;
not this text either;

Если это не так все, что вам нужно, - это опубликовать новый вопрос, включающий ввод, для которого вышеописанное не работает, и пометить его с помощью awk. Но не пытайтесь делать подобные вещи с помощью sed, это просто неподходящий инструмент для работы.

1 голос
/ 11 марта 2020

Когда вы используете спецификацию диапазона адресов, а затем вводите руководство l oop ниже в /PATTERN1 /I{, оно конфликтует с диапазоном адресов.

Пример. например:

seq 5 | sed -n '/1/,/3/{s/^/A/;p}; /1/{n;:a;/3/!{N;ba};p;}'

Каждый диапазон адресов «запоминает», введен он или нет, и следующая команда все равно выполняется. Если вы читаете до ; вручную, используя N или n в руководстве l oop, то диапазон адресов будет ждать, пока следующий ; не подойдет, чтобы прекратить ввод.

Если вы сами делаете l oop между PATTERN1 и ;, просто все равно удалите ^[ \t]* после новой строки.

D удаляет до первой новой строки в пространстве шаблона, поэтому после вы удалили все символы новой строки s/\n/ /, это эффективно удалит все.

Я думаю, вы захотите:

# if pattern is found
/PATTERN1 /I{
     # remove leading whitespaces 
     # I prefer [[:space:]]*
     s/^[ \t]*//
     # buffer everything until ';' is found
     :a; /;/!{N;ba;};
     # remove leading whitespaces after a newline
     s/\n[ \t]*/ /g; 
}
# remove the ( ... )
s/\(PATTERN1\) \([ \tA-Za-z0-9,\"\']*\)(\(.*\))[ \t]*;[ \t]*$/\1 \2\3;/I

, который выдает :

    not (this line);
pattern1 some text,  some other text (5), some other text (6);
pattern1 this text  that text (6), that text (7), ;
not this text either;

который выводит:

...