Saya ingin mengganti hanya k
contoh kata pertama.
Bagaimana saya bisa melakukan ini?
Misalnya. Katakanlah file foo.txt
berisi 100 kejadian kata 'linux'.
Saya perlu mengganti 50 kejadian pertama saja.
Saya ingin mengganti hanya k
contoh kata pertama.
Bagaimana saya bisa melakukan ini?
Misalnya. Katakanlah file foo.txt
berisi 100 kejadian kata 'linux'.
Saya perlu mengganti 50 kejadian pertama saja.
Jawaban:
Bagian pertama di bawah ini menjelaskan penggunaan sed
untuk mengubah kejadian-k pertama pada suatu garis. Bagian kedua memperluas pendekatan ini untuk mengubah hanya kejadian-k pertama dalam file, terlepas dari apa baris mereka muncul.
Dengan sed standar, ada perintah untuk mengganti kemunculan kata ke-k pada sebuah baris. Jika k
3, misalnya:
sed 's/old/new/3'
Atau, seseorang dapat mengganti semua kejadian dengan:
sed 's/old/new/g'
Tidak satu pun dari ini yang Anda inginkan.
GNU sed
menawarkan ekstensi yang akan mengubah kejadian k-th dan semuanya setelah itu. Jika k adalah 3, misalnya:
sed 's/old/new/g3'
Ini dapat digabungkan untuk melakukan apa yang Anda inginkan. Untuk mengubah 3 kejadian pertama:
$ echo old old old old old | sed -E 's/\<old\>/\n/g4; s/\<old\>/new/g; s/\n/old/g'
new new new old old
di mana \n
berguna di sini karena kita dapat yakin bahwa itu tidak pernah terjadi pada suatu garis.
Kami menggunakan tiga sed
perintah substitusi:
s/\<old\>/\n/g4
Ini ekstensi GNU untuk menggantikan yang keempat dan semua kejadian berikutnya old
dengan \n
.
Fitur regex diperpanjang \<
digunakan untuk mencocokkan awal kata dan \>
untuk mencocokkan akhir kata. Ini memastikan bahwa hanya kata-kata lengkap yang cocok. Regex yang diperluas membutuhkan -E
opsi untuk sed
.
s/\<old\>/new/g
Hanya tiga kejadian pertama yang old
tersisa dan ini menggantikan semuanya new
.
s/\n/old/g
Kejadian keempat dan semua yang tersisa old
digantikan dengan \n
pada langkah pertama. Ini mengembalikan mereka ke keadaan semula.
Jika GNU sed tidak tersedia dan Anda ingin mengubah 3 kejadian pertama old
menjadi new
, maka gunakan tiga s
perintah:
$ echo old old old old old | sed -E -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'
new new new old old
Ini bekerja dengan baik ketika k
sejumlah kecil tetapi skala buruk ke besar k
.
Karena beberapa sed non-GNU tidak mendukung menggabungkan perintah dengan titik koma, setiap perintah di sini diperkenalkan dengan -e
opsi sendiri . Mungkin juga perlu untuk memverifikasi bahwa Anda sed
mendukung simbol batas kata, \<
dan \>
.
Kita dapat meminta sed untuk membaca seluruh file dan kemudian melakukan penggantian. Misalnya, untuk mengganti tiga kejadian pertama old
menggunakan sed gaya BSD:
sed -E -e 'H;1h;$!d;x' -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'
Perintah sed H;1h;$!d;x
membaca seluruh file di.
Karena di atas tidak menggunakan ekstensi GNU, itu harus bekerja pada BSD (OSX) sed. Perhatikan, pikirkan, bahwa pendekatan ini membutuhkan sed
yang dapat menangani garis panjang. GNU sed
seharusnya baik-baik saja. Mereka yang menggunakan versi non-GNU sed
harus menguji kemampuannya untuk menangani antrean panjang.
Dengan sed GNU, kita dapat lebih lanjut menggunakan g
trik yang dijelaskan di atas, tetapi dengan \n
diganti dengan \x00
, untuk mengganti tiga kejadian pertama:
sed -E -e 'H;1h;$!d;x; s/\<old\>/\x00/g4; s/\<old\>/new/g; s/\x00/old/g'
Pendekatan ini berskala juga k
menjadi besar. Ini mengasumsikan, bahwa \x00
itu tidak ada dalam string asli Anda. Karena tidak mungkin untuk menempatkan karakter \x00
dalam string bash, ini biasanya merupakan asumsi yang aman.
tr '\n' '|' < input_file | sed …
. Tetapi, tentu saja, itu mengubah seluruh input menjadi satu baris, dan beberapa sed non-GNU tidak dapat menangani garis panjang yang sewenang-wenang. (2) Anda berkata, "... di atas, string yang dikutip '|'
harus diganti oleh karakter apa pun, atau string karakter, ..." Tetapi Anda tidak dapat menggunakan tr
untuk mengganti karakter dengan string (panjang> 1). (3) Dalam contoh terakhir Anda, Anda katakan -e 's/\<old\>/new/' -e 's/\<old\>/w/' | tr '\000' '\n'\>/new
. Ini sepertinya salah ketik untuk -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/' | tr '\000' '\n'
.
Perintah awk dapat digunakan untuk mengganti N kejadian pertama kata dengan penggantian.
Perintah hanya akan menggantikan jika kata tersebut benar-benar cocok.
Dalam contoh di bawah ini, saya mengganti 27
kejadian pertama old
dengannew
Menggunakan sub
awk '{for(i=1;i<=NF;i++){if(x<27&&$i=="old"){x++;sub("old","new",$i)}}}1' file
Perintah ini melewati setiap bidang hingga cocok
old
, memeriksa penghitung di bawah 27, peningkatan dan mengganti kecocokan pertama pada baris. Kemudian pindah ke bidang / baris berikutnya dan ulangi.
Mengganti bidang secara manual
awk '{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file
Mirip dengan perintah sebelumnya tetapi karena sudah memiliki penanda di bidang mana itu hingga
($i)
, itu hanya mengubah nilai bidang dariold
kenew
.
Melakukan pemeriksaan sebelumnya
awk '/old/&&x<27{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file
Memeriksa bahwa baris berisi yang lama dan penghitung di bawah 27
SHOULD
memberikan dorongan kecepatan kecil karena tidak akan memproses garis ketika ini salah.
HASIL
Misalnya
old bold old old old
old old nold old old
old old old gold old
old gold gold old old
old old old man old old
old old old old dog old
old old old old say old
old old old old blah old
untuk
new bold new new new
new new nold new new
new new new gold new
new gold gold new new
new new new man new new
new new new new dog new
new new old old say old
old old old old blah old
Katakanlah Anda ingin mengganti hanya tiga contoh pertama dari string ...
seq 11 100 311 |
sed -e 's/1/\
&/g' \ #s/match string/\nmatch string/globally
-e :t \ #define label t
-e '/\n/{ x' \ #newlines must match - exchange hold and pattern spaces
-e '/.\{3\}/!{' \ #if not 3 characters in hold space do
-e 's/$/./' \ #add a new char to hold space
-e x \ #exchange hold/pattern spaces again
-e 's/\n1/2/' \ #replace first occurring '\n1' string w/ '2' string
-e 'b t' \ #branch back to label t
-e '};x' \ #end match function; exchange hold/pattern spaces
-e '};s/\n//g' #end match function; remove all newline characters
catatan: di atas kemungkinan tidak akan berfungsi dengan komentar yang disematkan
... atau dalam contoh kasus saya, dari '1' ...
22
211
211
311
Di sana saya menggunakan dua teknik penting. Di tempat pertama setiap kemunculan 1
pada satu baris diganti dengan \n1
. Dengan cara ini, ketika saya melakukan penggantian rekursif berikutnya, saya bisa pastikan untuk tidak mengganti kejadian dua kali jika string pengganti saya berisi string pengganti saya. Misalnya, jika saya ganti he
dengan hey
itu masih akan berfungsi.
Saya melakukan ini seperti:
s/1/\
&/g
Kedua, saya menghitung penggantian dengan menambahkan karakter ke h
ruang lama untuk setiap kejadian. Begitu saya mencapai tiga tidak ada lagi terjadi. Jika Anda menerapkan ini pada data Anda dan mengubah \{3\}
ke penggantian total yang Anda inginkan dan /\n1/
alamat untuk apa pun yang Anda ingin ganti, Anda harus mengganti hanya sebanyak yang Anda inginkan.
Saya hanya melakukan semua -e
hal untuk dibaca. POSIXly Dapat ditulis seperti ini:
nl='
'; sed "s/1/\\$nl&/g;:t${nl}/\n/{x;/.\{3\}/!{${nl}s/$/./;x;s/\n1/2/;bt$nl};x$nl};s/\n//g"
Dan dengan GNU sed
:
sed 's/1/\n&/g;:t;/\n/{x;/.\{3\}/!{s/$/./;x;s/\n1/2/;bt};x};s/\n//g'
Ingat juga bahwa sed
ini berorientasi garis - tidak membaca di seluruh file dan kemudian mencoba untuk mengulanginya seperti yang sering terjadi pada editor lain. sed
sederhana dan efisien. Yang mengatakan, sering kali nyaman untuk melakukan sesuatu seperti berikut:
Berikut adalah fungsi shell kecil yang membundelnya menjadi perintah yang dieksekusi sederhana:
firstn() { sed "s/$2/\
&/g;:t
/\n/{x
/.\{$(($1))"',\}/!{
s/$/./; x; s/\n'"$2/$3"'/
b t
};x
};s/\n//g'; }
Maka dengan itu saya bisa melakukan:
seq 11 100 311 | firstn 7 1 5
... dan dapatkan ...
55
555
255
311
...atau...
seq 10 1 25 | firstn 6 '\(.\)\([1-5]\)' '\15\2'
...mendapatkan...
10
151
152
153
154
155
16
17
18
19
20
251
22
23
24
25
... atau, untuk mencocokkan contoh Anda (dengan urutan yang lebih kecil) :
yes linux | head -n 10 | firstn 5 linux 'linux is an os kernel'
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux
linux
linux
linux
linux
Alternatif singkat di Perl:
perl -pe 'BEGIN{$n=3} 1 while s/old/new/ && ++$i < $n' your_file
Ubah nilai `$ n $ sesuai keinginan Anda.
Bagaimana itu bekerja:
new
untuk old
( s/old/new/
) dan kapan pun bisa, itu akan menambahkan variabel $i
( ++$i
).1 while ...
) selama itu telah membuat kurang dari $n
total substitusi dan dapat membuat setidaknya satu substitusi pada baris itu.Gunakan loop shell dan ex
!
{ for i in {1..50}; do printf %s\\n '0/old/s//new/'; done; echo x;} | ex file.txt
Ya, ini agak konyol.
;)
Catatan: Ini mungkin gagal jika ada kurang dari 50 contoh old
dalam file. (Saya belum mengujinya.) Jika demikian, itu akan membuat file tidak dimodifikasi.
Lebih baik lagi, gunakan Vim.
vim file.txt
qqgg/old<CR>:s/old/new/<CR>q49@q
:x
Penjelasan:
q # Start recording macro
q # Into register q
gg # Go to start of file
/old<CR> # Go to first instance of 'old'
:s/old/new/<CR> # Change it to 'new'
q # Stop recording
49@q # Replay macro 49 times
:x # Save and exit
Solusi sederhana, tetapi tidak terlalu cepat adalah untuk mengulang perintah yang dijelaskan dalam /programming/148451/how-to-use-sed-to-replace-only-the-first-occurrence-in-a -mengajukan
for i in $(seq 50) ; do sed -i -e "0,/oldword/s//newword/" file.txt ; done
Perintah sed khusus ini mungkin hanya berfungsi untuk GNU sed dan jika newword bukan bagian dari oldword . Untuk non-GNU lihat di sini cara mengganti hanya pola pertama dalam file.
Dengan GNU awk
Anda dapat mengatur pemisah rekaman RS
ke kata yang akan diganti dibatasi oleh batas kata. Maka itu adalah kasus pengaturan pemisah rekaman pada output ke kata pengganti untuk k
catatan pertama sambil mempertahankan pemisah rekaman asli untuk sisanya.
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, NR <= limit? replacement: RT}' file
ATAU
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, limit--? replacement: RT}' file