Bagaimana cara mengganti hanya kemunculan pola ke-N dalam file?


10

Cara mengganti kemunculan ketiga string dalam file menggunakan sedperintah.

Contoh:

Ubah hanya kejadian ketiga iske usdalam file.

File input saya mengandung:

hai this is linux.
hai this is unix.
hai this is mac.
hai this is unchanged.

Saya mengharapkan output adalah:

hai this is linux.
hai thus is unix.
hai this is mac.
hai this is unchanged.

3
Input dan output sama.
Hauke ​​Laging

4
sedbukan alat yang tepat untuk pekerjaan itu.
choroba

@don_crissti saya memperbaikinya. OP tidak menggunakan alat pemformatan (Omong-omong, Sureshkumar, lihat di sini untuk bantuan dalam mengedit pertanyaan Anda) dan editor berturut-turut salah paham tentang apa yang diinginkan.
terdon

Jawaban:


11

Ini jauh lebih mudah dilakukan perl.

Untuk mengubah 3 rd terjadinya:

perl -pe 's{is}{++$n == 3 ? "us" : $&}ge'

Untuk mengubah setiap 3 rd terjadinya:

perl -pe 's{is}{++$n % 3 ? $& : "us"}ge'

3

Ketika string pengganti hanya muncul sekali per baris, Anda dapat menggabungkan berbagai utilitas.
Ketika input ada di file "input" dan Anda mengganti "is" by "us", Anda dapat menggunakan

LINENR=$(cat input | grep -n " is " | head -3 | tail -1 | cut -d: -f1)
cat input | sed ${LINENR}' s/ is / us /'

Dalam contoh dalam pertanyaan, ada lebih dari satu isper baris.
terdon

Saya pikir Anda sedang mencari "is" dengan spasi. Saya bisa mengedit jawaban saya dengan perintah tr seperti @jimmij digunakan, tetapi solusi saya akan menjadi jauh lebih rendah daripada miliknya.
Walter A

Saya bukan penanya :). Saya memikirkan hal yang sama, itulah sebabnya saya telah memutakhirkan jawaban Anda, tetapi jika Anda melihat versi asli dari pertanyaan (klik tautan "Diedit X menit yang lalu") Anda akan melihat bahwa OP mengharapkan ada di dalam ini diubah menjadi demikian . Ngomong-ngomong, tidak perlu ada kucing di sana.
terdon

2

Script di bawah ini (menggunakan sintaksis GNU sed ) dapat digunakan untuk mengedit inplace bukan untuk output karena menghentikan garis cetak setelah substitusi yang diinginkan:

sed -i '/is/{: 1 ; /\(.*is\)\{3\}/!{N;b1} ; s/is/us/3 ; q}' text.file

Jika Anda menyukai choroba, Anda dapat memodifikasi di atas

sed '/is/{:1 ; /\(.*is\)\{3\}/!{N;b1} ; s/is/us/3 ; :2 ; n ; $!b2}' text.file

yang menghasilkan semua lini

Atau Anda harus meletakkan semua garis di ruang pola (dalam memori jadi hati-hati dengan batasan ukuran) dan lakukan penggantian

sed ': 1 ; N ; $!b1 ; s/is/us/3 ' text.file

2

Anda dapat menggunakannya seduntuk itu jika sebelumnya baris baru diganti dengan karakter lain, misalnya:

tr '\n' '\000' | sed 's/is/us/3' | tr '\000' '\n'

Dan hal yang sama dengan pure (GNU) sed:

sed ':a;N;$!ba;s/\n/\x0/g;s/is/us/3;s/\x0/\n/g'

( sedPenggantian baris baru dicuri tanpa malu-malu dari https://stackoverflow.com/a/1252191/4488514 )


Jika Anda akan menggunakan sedsintaksis khusus GNU , Anda mungkin juga menggunakannya sed -z 's/is/us/3'.
Stéphane Chazelas

@ StéphaneChazelas -zpasti merupakan fitur baru, saya GNU sed version 4.2.1tidak tahu apa-apa tentang opsi ini.
jimmij

1
Ditambahkan dalam 4.2.2 (2012). Dalam solusi kedua Anda, Anda tidak perlu konversi untuk \x0melangkah.
Stéphane Chazelas

Maaf tentang hasil edit. Saya belum melihat versi asli pertanyaan dan seseorang telah salah paham dan mengedit baris yang salah. Saya kembali ke versi sebelumnya.
terdon

1
p='[:punct:]' s='[:space:]'
sed -Ee'1!{/\n/!b' -e\}            \
     -e's/(\n*)(.*)/ \2 \1/'       \
     -e"s/is[$p]?[$s]/\n&/g"       \
     -e"s/([^$s])\n/\1/g;1G"       \
-e:c -e"s/\ni(.* )\n{3}/u\1/"      \
     -e"/\n$/!s/\n//g;/\ni/G"      \
     -e's//i/;//tc'                \
     -e's/^ (.*) /\1/;P;$d;N;D'

Sedikit sedsaja membawa penghitungan iskejadian dari satu baris ke yang berikutnya. Ini seharusnya dapat menangani sebanyak mungkin ises per baris saat Anda melemparkannya, dan itu tidak perlu buffer garis lama sementara itu - itu hanya mempertahankan satu karakter baris baru untuk setiap isyang ia temui yang bukan bagian dari kata lain.

Hasilnya adalah ia akan memodifikasi hanya kejadian ketiga dalam file - dan itu akan membawa jumlah per baris. Jadi jika sebuah file terlihat seperti:

1. is is isis
2. is does

... itu akan mencetak ...

1. is is isis
2. us does

Pertama-tama menangani kasing tepi dengan memasukkan spasi di kepala dan ekor setiap garis. Ini membuat batasan kata sedikit lebih mudah untuk dipastikan.

Selanjutnya mencari ises yang valid dengan memasukkan \ngaris sebelum sebelum semua kejadian isyang mendahului nol atau satu karakter tanda baca diikuti oleh spasi. Itu lulus lain dan menghapus semua \newline yang segera didahului oleh karakter bukan spasi. Penanda yang ditinggalkan ini akan cocok is.dan istetapi tidak thisatau ?is.

Selanjutnya mengumpulkan setiap penanda ke ekor tali - untuk setiap \nipertandingan pada garis itu menambahkan \ngaris ke ekor tali dan menggantinya dengan salah satu iatau u. Jika ada 3 \nbaris dalam satu baris berkumpul di ujung string maka ia menggunakan u - selain itu i. Au pertama kali digunakan juga yang terakhir - penggantian memicu loop tak terbatas yang bermuara ke get line, print line, get line, print line,dan seterusnya.

Pada akhir setiap siklus loop coba, ia membersihkan ruang yang disisipkan, mencetak hanya hingga baris baru yang pertama kali terjadi dalam ruang pola, dan berjalan lagi.

Saya akan menambahkan lperintah ook di bagian atas loop seperti:

l; s/\ni(.* )\n{9}/u\1/...

... dan lihat apa fungsinya saat bekerja dengan input ini:

hai this is linux.
hai this is unix.


hai this is mac.
hai this is unchanged is.

... jadi inilah fungsinya:

 hai this \nis linux. \n$        #behind the scenes
hai this is linux.               #actually printed
 hai this \nis unix. \n\n$       #it builds the marker string
hai this is unix.
  \n\n\n$                        #only for lines matching the

  \n\n\n$                        #pattern - and not otherwise.

 hai this \nis mac. \n\n\n$      #here's the match - 3 ises so far in file.
hai this us mac.                 #printed
hai this is unchanged is.        #no look here - this line is never evaled

Lebih masuk akal mungkin dengan lebih banyak ises per baris:

nthword()(  p='[:punct:]' s='[:space:]'         
    sed -e '1!{/\n/!b' -e\}             \
        -e 's/\(\n*\)\(.*\)/ \2 \1/'    \
        -e "s/$1[$p]\{0,1\}[$s]/\n&/g"  \
        -e "s/\([^$s]\)\n/\1/g;1G;:c"   \
        -e "${dbg+l;}s/\n$1\(.* \)\n\{$3\}/$2\1/" \
        -e '/\n$/!s/\n//g;/\n'"$1/G"    \
        -e "s//$1/;//tc" -e 's/^ \(.*\) /\1/'     \
        -e 'P;$d;N;D'
)        

Praktis hal yang sama tetapi ditulis dengan POSIX BRE dan penanganan argumen yang belum sempurna.

 printf 'is is. is? this is%.0s\n' {1..4}  | nthword is us 12

...mendapat...

is is. is? this is
is is. is? this is
is is. is? this us
is is. is? this is

... dan jika saya mengaktifkan ${dbg}:

printf 'is is. is? this is%.0s\n' {1..4}  | 
dbg=1 nthword is us 12

... kita bisa menontonnya iterate ...

 \nis \nis. \nis? this \nis \n$
 is \nis. \nis? this \nis \n\n$
 is is. \nis? this \nis \n\n\n$
 is is. is? this \nis \n\n\n\n$
is is. is? this is
 \nis \nis. \nis? this \nis \n\n\n\n\n$
 is \nis. \nis? this \nis \n\n\n\n\n\n$
 is is. \nis? this \nis \n\n\n\n\n\n\n$
 is is. is? this \nis \n\n\n\n\n\n\n\n$
is is. is? this is
 \nis \nis. \nis? this \nis \n\n\n\n\n\n\n\n\n$
 is \nis. \nis? this \nis \n\n\n\n\n\n\n\n\n\n$
 is is. \nis? this \nis \n\n\n\n\n\n\n\n\n\n\n$
 is is. is? this \nis \n\n\n\n\n\n\n\n\n\n\n\n$
is is. is? this us
is is. is? this is

Apakah Anda menyadari contoh Anda mengatakan "isis"?
flarn2006

@ flarn2006 - saya cukup yakin ia mengatakan apa adanya.
mikeserv

0

Berikut adalah solusi logis yang menggunakan seddan trtetapi harus ditulis dalam skrip agar dapat berfungsi. Kode di bawah ini menggantikan setiap kemunculan kata yang ditentukan dalam sedperintah. Ganti i=3dengan i=nuntuk membuat ini berfungsi untuk siapa pun n.

Kode:

# replace new lines with '^' character to get everything onto a single line
tr '\n' '^' < input.txt > output.txt

# count number of occurrences of the word to be replaced
num=`grep -o "apple" "output.txt" | wc -l`

# in successive iterations, replace the i + (n-1)th occurrence
n=3
i=3
while [ $i -le $num ]
do
    sed -i '' "s/apple/lemon/${i}" 'output.txt'
    i=$(( i + (n-1) ))
done

# replace the '^' back to new line character
tr '^' '\n' < output.txt > tmp && mv tmp output.txt


Mengapa ini bekerja:

Misalkan file teksnya a b b b b a c a d a b b b a b e b z b s b a b.

  • Ketika n = 2: kami ingin mengganti setiap kemunculan kedua b.

    • a b b b b a c a d a b b b a b e b z b s b a b
      . . ^ . ^ . . . . . . ^ . . ^ . . . ^ . ^ . ^
    • Pertama kita mengganti kejadian 2, lalu kejadian 3, lalu ke 4, 5, dan seterusnya. Hitung dalam urutan yang ditunjukkan di atas untuk melihat ini sendiri.
  • Ketika n = 3: kami ingin mengganti setiap kemunculan ketiga b.

    • a b b b b a c a d a b b b a b e b z b s b a b
      . . . ^ . . . . . . . ^ . . . . ^ . . . . . ^
    • Pertama kita mengganti kejadian ke-3, lalu ke-5, lalu ke-7, ke-9, ke-11, dan seterusnya.
  • Ketika n = 4: kami ingin mengganti setiap kemunculan ketiga b.

    • Pertama kita mengganti kejadian ke-4, lalu ke-7, lalu ke-10, ke-13, dan seterusnya.
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.