Ganti string dengan indeks berurutan

10

Dapatkah seseorang menyarankan cara yang elegan untuk mencapai ini?

Memasukkan:

test  instant  ()

test  instant  ()

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

output harus:

test      instant1  ()

test      instant2  ()

test      instant1000()

Baris kosong ada di file input saya dan ada banyak file di bawah direktori yang sama yang harus saya proses sekaligus.

Saya mencoba ini untuk mengganti banyak file dalam direktori yang sama dan tidak berfungsi.

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

kesalahan:

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

dan saya juga mencoba ini:

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

Ini berhasil tetapi indeks terus bertambah dari satu ke file lain. Saya ingin mengatur ulang ke 1 setelah perubahan ke file baru. Ada saran bagus?

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

berfungsi tetapi diganti semua file lain tidak boleh diganti. Saya lebih suka hanya mengganti file dengan *.txtsaja.

pengguna3342338
sumber
Dan apakah mereka semua hanya terdiri dari baris kosong atau test instant ()?
terdon
Saya meletakkan garis spasi ganda kembali, mereka sering kali merupakan tanda pengguna baru tidak tahu bagaimana menggunakan markup situs ini, itu sebabnya terdon menghapusnya sambil dengan benar mengindentasi blok konten file Anda sehingga ditampilkan sebagai konten file. Semoga tidak apa-apa sekarang.
Timo

Jawaban:

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

atau dengan GNU awk:

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

Untuk mengedit file di tempat, tambahkan -iopsi ke perl:

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

Atau secara rekursif:

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

Penjelasan

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

-padalah memproses input baris demi baris, mengevaluasi ekspresi yang diteruskan ke -euntuk setiap baris dan mencetaknya. Untuk setiap baris, kami mengganti (menggunakan s/re/repl/flagsoperator) instantuntuk dirinya sendiri ( $&) dan nilai variabel yang ditambahkan ++$n. The gbendera adalah untuk membuat substitusi global (tidak hanya sekali), dan esehingga penggantian ditafsirkan sebagai kode perl untuk e valuate (bukan string tetap).

Untuk pengeditan di tempat di mana satu perl memproses lebih dari satu file, kami ingin $nmengatur ulang di setiap file. Sebagai gantinya, kami menggunakan $n{$ARGV}(di mana $ARGVfile yang saat ini diproses).

Yang awkpantas sedikit penjelasan.

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

Kami menggunakan kemampuan GNU awkuntuk memisahkan catatan pada string arbitrer (bahkan regexps). Dengan -vRS=instant, kami mengatur s̲eparator r̲ecord ke instant. RTadalah variabel yang menyimpan apa yang cocok dengan RS, jadi biasanya, instantkecuali untuk catatan terakhir di mana itu akan menjadi string kosong. Dalam input di atas record ( $0) dan terminator record ( RT) adalah ( [$0|RT]):

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

Jadi yang perlu kita lakukan adalah memasukkan angka tambahan di awal setiap catatan kecuali yang pertama.

Itulah yang kami lakukan di atas. Untuk catatan pertama, nakan kosong. Kami mengatur ORS (the̲utput r̲ecord s̲eparator ) ke RT, sehingga awk tercetak n $0 RT. Itu melakukannya pada ekspresi kedua ( ++n) yang merupakan kondisi yang selalu bernilai true (angka bukan nol), dan karenanya tindakan default (pencetakan $0 ORS) dilakukan untuk setiap catatan.

Stéphane Chazelas
sumber
4
Ini bisa menggunakan sedikit penjelasan .
Gilles 'SO- stop being evil'
5

sedbenar-benar bukan alat terbaik untuk pekerjaan itu, Anda menginginkan sesuatu dengan kemampuan skrip yang lebih baik. Berikut ini beberapa pilihan:

  • perl

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

    The -pberarti "mencetak setiap baris" setelah menerapkan script apapun yang diberikan dengan -e. The -00bergantian pada "mode ayat" sehingga catatan (baris) didefinisikan oleh baris berturut-turut ( \n) karakter, ini memungkinkan itu berurusan dengan garis spasi ganda dengan benar. $&adalah pola terakhir yang cocok dan $.merupakan nomor baris saat ini dari file input. The edalam s///ememungkinkan saya untuk mengevaluasi ekspresi di operator substitusi.

  • awk (ini mengasumsikan data Anda persis seperti yang ditunjukkan, dengan tiga bidang yang dipisahkan ruang)

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

    Di sini, kami menambah kvariabel khanya jika baris saat ini tidak kosong /./dalam hal ini kami juga mencetak info yang diperlukan. Garis kosong dicetak apa adanya.

  • berbagai kerang

     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 

    Di sini, setiap baris input secara otomatis dibagi pada spasi putih dan bidang disimpan sebagai $a, $bdan $c. Kemudian, di dalam loop, $cditambah satu untuk setiap baris yang $atidak kosong dan nilainya saat ini dicetak di sebelah bidang kedua $b,.

CATATAN: semua solusi di atas menganggap bahwa semua baris dalam file memiliki format yang sama. Jika tidak, jawaban @ Stephane adalah caranya.


Untuk berurusan dengan banyak file, dan dengan asumsi bahwa Anda ingin melakukan ini untuk semua file di direktori saat ini, Anda dapat menggunakan ini:

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

HATI-HATI: Itu mengasumsikan nama file sederhana tanpa spasi, jika perlu berurusan dengan sesuatu yang lebih kompleks, gunakan (dengan asumsi ksh93, zshatau bash):

find . -type f -print0 | while IFS= read -r -d ''; do
    perl -i -00pe 's/instant/$& . $./e' "$file"
done
terdon
sumber
skrip perl berfungsi. Namun ada satu masalah kecil jika garis-garisnya adalah ruang ganda.
user3342338
@ user3342338 ya, itu akan menambah penghitung karena saya menggunakan nomor baris saat ini. Ini adalah pendekatan yang sangat naif, karena saya katakan Stephane lebih kuat. Tak satu pun dari ini bekerja jika Anda memiliki garis kosong atau jika ada garis Anda menyimpang dari apa yang Anda tampilkan.
terdon
@ user3342338 lihat jawaban yang diperbarui. Mereka semua sekarang harus bekerja untuk file spasi ganda.
terdon
Jawaban yang bagus dan opsi metode alternatif !! Terima kasih
Madivad
0

Jika Anda ingin menyelesaikan ini dengan sedAnda dapat menggunakan sesuatu seperti ini bash:

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

atau solusi yang lebih portabel adalah:

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
sumber