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


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.


І чи всі вони складаються виключно з порожніх рядків або test instant ()?
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, ми встановлюємо роздільник в instant. RT- це змінна, яка містить те, з чим узгоджено RS, так зазвичай, instantза винятком останнього запису, де це буде порожній рядок. На вході над записами ( $0) та термінаторами запису ( RT) знаходяться ( [$0|RT]):

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

Отже, все, що нам потрібно зробити, - це вставити наростаюче число на початку кожного запису, окрім першого.

Що ми робимо вище. Для першого запису nбуде порожнім. Встановлюємо ORS (програму s̲eparator o̲utput r̲ecord ) на RT, так що awk друкується n $0 RT. Це робиться при другому виразі ( ++n), що є умовою, що завжди оцінюється як істинне (ненульове число), і тому дія кожного тижня $0 ORSвиконується за замовчуванням (друку ).



5

sedнасправді не найкращий інструмент для роботи, ви хочете щось з кращими можливостями сценарію. Ось кілька варіантів:

  • перл

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

    В -pозначає «друк кожну рядок» після застосування будь-якого сценарію даються з -e. У -00черзі на режимі «пункт» так записи (рядки) визначаються шляхом послідовним перекладом рядка ( \n) символи, це дозволяє йому мати справу з подвійними відстають один від одного ліній правильно. $&- це останній узгоджений шаблон і $.є поточним номером рядка вхідного файлу. eВ 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.

ПРИМІТКА. Усі вищезазначені рішення передбачають, що всі рядки у файлі мають однаковий формат. Якщо ні, то відповідь @ Стефана - це шлях.


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

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 працює. однак є одна невелика проблема, якщо лінії є подвійним пробілом.
користувач3342338

@ 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.