Zamień ciąg na indeks sekwencyjny

10

Czy ktoś może zasugerować elegancki sposób na osiągnięcie tego?

Wejście:

test  instant  ()

test  instant  ()

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

wyjście powinno być:

test      instant1  ()

test      instant2  ()

test      instant1000()

Puste wiersze znajdują się w moich plikach wejściowych i w tym samym katalogu jest wiele plików, które muszę przetworzyć jednocześnie.

Próbowałem zastąpić wiele plików w tym samym katalogu i nie działałem.

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

błędy:

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

i próbowałem również:

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

Działało, ale indeks ciągle zwiększał się z jednego pliku do drugiego. Chciałbym zresetować to do 1 po zmianie na nowy plik. Jakieś dobre sugestie?

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

działa, ale zastąpił wszystkie inne pliki nie powinny być zastępowane. Wolę po prostu zastąpić pliki *.txttylko.

użytkownik3342338
źródło
I czy wszystkie składają się wyłącznie z pustych linii lub test instant ()?
terdon
Wstawiam ponownie wiersze z podwójnymi odstępami, często są znakiem, że nowi użytkownicy nie wiedzą, jak korzystać ze znaczników tej witryny, dlatego terdon usunął je, jednocześnie odpowiednio wcinając blok zawartości pliku, aby wyświetlał się jako zawartość pliku. Mam nadzieję, że teraz jest ok.
Timo

Odpowiedzi:

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

lub z GNU awk:

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

Aby edytować pliki w miejscu, dodaj -iopcję do perl:

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

Lub rekurencyjnie:

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

Objaśnienia

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

-ppolega na przetwarzaniu danych wejściowych linia po linii, ocenieniu wyrażenia przekazanego -edla każdej linii i wydrukowaniu go. Dla każdej linii podstawiamy (za pomocą s/re/repl/flagsoperatora) instantsamą siebie ( $&) i przyrostową wartość zmiennej ++$n. gFlaga jest, aby zmiany globalnie (nie tylko raz), a ewięc, że wymiana jest interpretowany jako perl kod do e wycenić (nie stałą string).

W przypadku edycji w miejscu, gdzie jedno wywołanie Perla przetwarza więcej niż jeden plik, chcemy $nzresetować każdy plik. Zamiast tego używamy $n{$ARGV}(gdzie $ARGVjest aktualnie przetwarzany plik).

Ten awkzasługuje na trochę wyjaśnienia.

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

Korzystamy ze zdolności GNU awkdo oddzielania rekordów na dowolnych ciągach (nawet wyrażeniach regularnych). Za pomocą -vRS=instantustawiamy separator r̲ecord na instant. RTjest zmienną, która przechowuje to, co zostało dopasowane RS, więc zazwyczaj, instantz wyjątkiem ostatniego rekordu, w którym będzie to pusty ciąg. W danych wejściowych powyżej record ( $0) i terminatorami record ( RT) są ( [$0|RT]):

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

Więc wszystko, co musimy zrobić, to wstawić liczbę rosnącą na początku każdego rekordu, z wyjątkiem pierwszego.

Co robimy powyżej. Dla pierwszego rekordu nbędzie pusty. Ustawiamy ORS (parametr wyjściowy rordecord s̲eparator ) na RT, aby awk drukował n $0 RT. Robi to na podstawie drugiego wyrażenia ( ++n), które jest warunkiem, który zawsze zwraca wartość true (liczba niezerowa), a zatem $0 ORSdla każdego rekordu wykonywana jest domyślna akcja (drukowania ).

Stéphane Chazelas
źródło
4
Przydałoby się to trochę wyjaśnienia .
Gilles „SO- przestań być zły”
5

sednaprawdę nie jest najlepszym narzędziem do pracy, potrzebujesz czegoś o lepszych możliwościach skryptowych. Oto kilka opcji:

  • perl

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

    Te -pśrodki „wydrukować każdą linię” po zastosowaniu co skrypt jest podane z -e. Te -00zakręty w trybie „ustęp” So rekordów (wierszy) są definiowane przez kolejny znak nowej linii ( \n) znaków, to pozwala poradzić sobie z podwójnymi rozstawionych linii poprawnie. $&jest ostatnim dopasowanym wzorcem i $.jest bieżącym numerem wiersza pliku wejściowego. Funkcja ein s///epozwala mi oceniać wyrażenia w operatorze podstawienia.

  • awk (zakłada to, że twoje dane są dokładnie takie, jak pokazano, z trzema polami oddzielonymi spacjami)

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

    Tutaj zwiększamy kzmienną ktylko wtedy, gdy bieżący wiersz nie jest pusty, /./w którym to przypadku drukujemy również niezbędne informacje. Puste linie są drukowane bez zmian.

  • różne muszle

     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 

    Tutaj każdy wiersz wejściowy jest automatycznie dzielony na białe znaki, a pola są zapisywane jako $a, $bi $c. Następnie w pętli, $cjest zwiększona o jeden dla każdej linii, dla których $anie jest pusty i jest aktualna wartość zostanie wydrukowany obok drugiego pola $b.

UWAGA: wszystkie powyższe rozwiązania zakładają, że wszystkie wiersze w pliku mają ten sam format. Jeśli nie, odpowiedź @ Stephane jest właściwą drogą.


Do obsługi wielu plików i zakładania, że ​​chcesz to zrobić dla wszystkich plików w bieżącym katalogu, możesz użyć tego:

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

OSTROŻNIE: To zakłada proste nazwy plików bez spacji, w razie potrzeby do czynienia z czymś bardziej złożonym, przejdź do (zakładając ksh93, zshalbo bash):

find . -type f -print0 | while IFS= read -r -d ''; do
    perl -i -00pe 's/instant/$& . $./e' "$file"
done
terdon
źródło
skrypt perla działa. jest jednak jeden mały problem, jeśli linie są podwójnymi spacjami.
user3342338
@ user3342338 tak, to zwiększy licznik, ponieważ używam bieżącego numeru linii. To bardzo naiwne podejście, ponieważ powiedziałem, że Stephane jest bardziej solidny. Żaden z nich nie działa, jeśli masz puste linie lub jeśli którakolwiek z linii odbiega od tego, co wyświetlasz.
terdon
@ user3342338 zobacz zaktualizowaną odpowiedź. Wszystkie powinny teraz działać dla plików z podwójnymi odstępami.
terdon
Świetna odpowiedź i opcja alternatywnych metod !! Dzięki
Madivad,
0

Jeśli chcesz rozwiązać ten problem, sedmożesz użyć czegoś takiego bash:

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

lub bardziej przenośnym rozwiązaniem byłoby:

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
źródło