Заменить строку с последовательным индексом

10

Может кто-нибудь предложить элегантный способ сделать это?

Входные данные:

test  instant  ()

test  instant  ()

...
test  instant  ()    //total 1000 lines

вывод должен быть:

test      instant1  ()

test      instant2  ()

test      instant1000()

Пустые строки находятся в моих входных файлах, и в одном каталоге много файлов, которые мне нужно обработать одновременно.

Я попытался это, чтобы заменить много файлов в том же каталоге и не работал.

for file in ./*; do perl -i -000pe 's/instance$& . ++$n/ge' "$file"; done

ошибки:

Substitution replacement not terminated at -e line 1.
Substitution replacement not terminated at -e line 1.

и я тоже попробовал это:

perl -i -pe 's/instant/$& . ++$n/ge' *.vs

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

find . -type f -exec perl -pi -e 's/instant/$& . ++$n{$ARGV}/ge' {} +

работает, но он заменил все остальные файлы не должны быть заменены. Я предпочитаю просто заменить файлы *.txtтолько.

user3342338
источник
И все они состоят исключительно из пустых строк или test instant ()?
Terdon
Я вставил строки с двойными интервалами обратно, они часто являются признаком того, что новые пользователи не знают, как использовать разметку этого сайта, поэтому terdon удалил их при правильном выравнивании блока содержимого файла, чтобы он отображался как содержимое файла. Надеюсь, теперь все в порядке.
Тимо

Ответы:

14
perl -pe 's/instant/$& . ++$n/ge'

или с GNU awk:

awk -vRS=instant '{$0=n$0;ORS=RT}++n'

Чтобы редактировать файлы на месте, добавьте -iпараметр в perl:

perl -pi -e 's/instant/$& . ++$n{$ARGV}/ge' ./*.vs

Или рекурсивно:

find . -name '*.vs' -type f -exec perl -pi -e '
  s/instant/$& . ++$n{$ARGV}/ge' {} +

Пояснения

perl -pe 's/instant/$& . ++$n/ge'

-pобрабатывать входные данные построчно, оценивать переданные выражения -eдля каждой строки и распечатывать их. Для каждой строки мы подставляем (используя s/re/repl/flagsоператор) instantдля себя ( $&) и увеличенное значение переменной ++$n. gФлаг , чтобы сделать замену во всем мире ( а не только один раз), и eтаким образом , что замена интерпретируется как PERL код для электронной valuate (не фиксированная строка).

Для редактирования на месте, когда один вызов perl обрабатывает более одного файла, мы хотим $nвыполнить сброс для каждого файла. Вместо этого мы используем $n{$ARGV}(где $ARGVнаходится текущий обрабатываемый файл).

Тот awkзаслуживает немного объяснения.

awk -vRS=instant '{$0=n$0;ORS=RT}++n'

Мы используем способность GNU awkразделять записи на произвольные строки (даже регулярные выражения). С помощью -vRS=instant, мы устанавливаем raraecord s̲eparator в instant. RTпеременная, которая содержит то, что было сопоставлено RS, как правило, instantза исключением последней записи, где она будет пустой строкой. Во входных данных выше записи ( $0) и терминаторы записи ( RT): ( [$0|RT]):

[test  |instant][  ()
test  |instant][  ()
...
test  |instant][  ()    //total 1000 lines|]

Таким образом, все, что нам нужно сделать, это вставить увеличивающийся номер в начале каждой записи, кроме первой.

Что мы и делаем выше. Для первой записи nбудет пусто. Мы устанавливаем ORS ( o̲utput r̲ecord s̲eparator ) в RT, чтобы awk печатать n $0 RT. Это происходит со вторым выражением ( ++n), которое является условием, которое всегда оценивается как истина (ненулевое число), и, следовательно, действие по умолчанию (печати $0 ORS) выполняется для каждой записи.

Стефан Шазелас
источник
4
Это может использовать немного объяснения .
Жиль "ТАК ... перестать быть злым"
5

sedдействительно не лучший инструмент для работы, вы хотите что-то с лучшими возможностями сценариев. Вот несколько вариантов:

  • Perl

    perl -00pe 's/instant/$& . $./e' file 

    В -pозначает «печать каждую строка» после применения любого сценария даются с -e. В -00очереди на режиме «пункт» так записи (строки) определяются путем последовательным переводом строки ( \n) символы, это позволяет ему иметь дело с двойными отстоящими друг от друга линий правильно. $&последний сопоставленный шаблон и $.номер текущей строки входного файла. Функция ein s///eпозволяет мне вычислять выражения в операторе подстановки.

  • awk (предполагается, что ваши данные точно такие, как показано, с тремя разделенными пробелами полями)

    awk '{if(/./) print $1,$2 ++k,$3; else print}' file 

    Здесь мы увеличиваем kпеременную, kтолько если текущая строка не пуста, и /./в этом случае мы также выводим необходимую информацию. Пустые строки печатаются как есть.

  • различные снаряды

     n=0; while read -r a b c; do 
       if [ "$a" ] ; then 
          (( n++ ))
          printf "%s %s%s %s\n" "$a" "$b" "$n" "$c"
       else
          printf "%s %s %s\n" "$a" "$b" "$c"
       fi
     done < file 

    Здесь каждая входная строка автоматически разбивается на пробельных и поля сохраняются как $a, $bи $c. Затем, в течение цикла, $cувеличивается на единицу для каждой строки , для которых $aне является пустым , и это текущее значение выводится рядом со вторым полем, $b.

ПРИМЕЧАНИЕ: все вышеприведенные решения предполагают, что все строки в файле имеют одинаковый формат. Если нет, то ответ @ Stephane - это путь.


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

for file in ./*; do perl -i -00pe 's/instant/$& . $./e' "$file"; done

ОСТОРОЖНО: Это предполагает , что простые имена файлов без пробелов, в случае необходимости иметь дело с чем - то более сложным, пойти (при условии ksh93, zshили bash):

find . -type f -print0 | while IFS= read -r -d ''; do
    perl -i -00pe 's/instant/$& . $./e' "$file"
done
Тердон
источник
скрипт perl работает. однако есть одна маленькая проблема, если строки имеют двойной пробел.
user3342338
@ user3342338 да, это увеличит счетчик, так как я использую текущий номер строки. Это очень наивный подход, так как я сказал, что Стефан более надежен. Ничто из этого не работает, если у вас есть пустые строки или если какие-либо из ваших строк отличаются от того, что вы показываете.
Terdon
@ user3342338 см обновленный ответ. Все они теперь должны работать для файлов с двойным интервалом.
Terdon
Отличный ответ и возможность альтернативных методов! Спасибо
Мадивад
0

Если вы хотите решить эту проблему, sedвы можете использовать что-то вроде этого (в bash):

i=0
while read -r line; do
  sed "s/\(instant\)/\1${i}/" <<< "${line}"
  [[ ${line} =~ instant ]] && i=$(( i + 1 ))
done < file

или более портативное решение будет:

i=0
while read -r line; do
  echo "${line}" | sed "s/\(instant\)/\1${i}/"
  if echo "${line}" | grep -q inst; then
    i=$(( i + 1 ))
  fi
done < file
noAnton
источник