Как сопоставить все совпадения в строке, кроме первых, с помощью sed?

Я делаю свои сообщения о фиксации в Git с определенным шаблоном, чтобы упростить создание журнала изменений для новых выпусков (https://stackoverflow.com/a/5151123/520162).

Каждое изменение, которое должно быть внесено в мой журнал изменений, имеет префикс CHG, NEW или FIX.

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

git show --quiet --date=short --pretty=format:"%cd %an %s%n%n%w(100,21,21)%b%n" $CURRENTREVISION

Субъект (%s) содержит предмет модификации.

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

Теперь случается, что в строке темы есть несколько вхождений CHG, NEW или FIX. Мой вывод темы выглядит так:

DATE NAME FIX first change NEW second change CHG third change

Я хотел бы добавить префикс новой строки ко всем ключевым словам, кроме первого, чтобы каждый CHG, NEW или FIX начинал новую строку:

DATE NAME FIX first change
          NEW second change
          CHG third change

Что я должен сказать SED, чтобы добиться этого?


person eckes    schedule 08.12.2014    source источник


Ответы (4)


sed не самый подходящий инструмент для этого

С awk это будет выглядеть так.

awk '{n=0; for (i=1; i<=NF; i++) {if ($i ~ /(NEW|FIX|CHG)/) {$i=(n++?"\n          ":"")$i}}}7'
  • n=0 (пере)установить флаг
  • for (i=1; i<=NF; i++) цикл по каждому полю строки
  • if ($i ~ /(NEW|FIX|CHG)/) if the field is one of the markers
    • $i=(n++?"\n ":"")$i update the field by adding the appropriate leading space (or none)
  • 7 шаблон истинности, чтобы распечатать текущую строку.
person Etan Reisner    schedule 08.12.2014
comment
голосование, поскольку этот ответ документирует оператор awk вместо игры в гольф с кодом! - person eckes; 08.12.2014

awk '{while(++i<=NF){if($i~/FIX|NEW|CHG/){if(f){$i="\n"$i}else{f=1}}}}1'

или даже меньше:

awk '{while(++i<=NF){if($i~/FIX|NEW|CHG/){if(f++){$i="\n"$i}}}}1'

Пример:

$echo "DATE CH NAME FIX first change NEW second change CHG third change" | awk '{while(++i<=NF){if($i~/FIX|NEW|CHG/){if(f){$i="\n"$i}else{f=1}}}}1'

DATE CH NAME FIX first change 
NEW second change 
CHG third change

Перейти из 1st to last полей. для любого поля, соответствующего любому из 3 шаблонов, мы проверяем, есть ли f=1, что будет ложным в случае первого совпадения. поскольку мы делаем f++, для следующих совпадений это будет верно, и, следовательно, "\n" будет добавлено раньше.

person Arjun Mathew Dan    schedule 08.12.2014

sed '/^DATE NAME/ {
:cycle
   s/\(.\{1,\}\) \(FIX .*\)/\1\
\2/g
   t cycle
   s/\(.\{1,\}\) \(NEW .*\)/\1\
\2/g
   t cycle
   s/\(.\{1,\}\) \(CHG .*\)/\1\
\2/g
   t cycle

   s/\n/&          /g
   s/\n */ /
   }' YourFile

что-то подобное для версии posix (--posix в GNU sed).

простой

   s/\(.\{1,\}\) \(\(CHG|FIX|NEW\) .*\)/\1\
\2/g
   t cycle

можно заменить 3 первых s/// на GNU sed, чтобы разрешить |

Я немного защищаю первый /^DATA NAME/ в качестве фильтра, но если обрабатывается только этот тип строки, в этом нет необходимости (и связанных { })

person NeronLeVelu    schedule 08.12.2014

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

вместо этого я думаю, что, возможно, Perl — фантастический инструмент для этого.

что-то вроде этого:

while(<STDIN>){
    my @matches = m/^(.*?)((?:FIX|NEW|CHG).*?)*$/;
    my $date_name = unshift @matches; # only FIX, NEW, CHG remains now
    print $date_name, unshift @matches;
    while (@matches) { print "\t\t", unshift @matches; }
}

передать ваши исходные данные и перенаправить в файл в оболочке.

person Jason Hu    schedule 08.12.2014