k
Bir kelimenin sadece ilk örneklerini değiştirmek istiyorum .
Bunu nasıl yapabilirim?
Örneğin. Diyelim ki dosya foo.txt
'linux' kelimesi için 100 örnek içermektedir.
Sadece ilk 50 örneği değiştirmem gerekiyor.
k
Bir kelimenin sadece ilk örneklerini değiştirmek istiyorum .
Bunu nasıl yapabilirim?
Örneğin. Diyelim ki dosya foo.txt
'linux' kelimesi için 100 örnek içermektedir.
Sadece ilk 50 örneği değiştirmem gerekiyor.
Yanıtlar:
Aşağıdaki ilk bölüm sed
, bir satırdaki ilk k oluşumunu değiştirmeyi açıklamaktadır . İkinci bölüm, hangi çizgide göründüklerine bakılmaksızın, bir dosyadaki yalnızca ilk k oluşumunu değiştirmek için bu yaklaşımı genişletir.
Standart sed ile, bir satırdaki kelimenin k-inci oluşumunu değiştirme komutu vardır. Eğer k
, örneğin, 3,:
sed 's/old/new/3'
Veya, tüm olayları aşağıdakilerle değiştirebilirsiniz:
sed 's/old/new/g'
Bunların hiçbiri istediğin şey değil.
GNU sed
, ikinci ve sonrasındaki gelişmeleri değiştirecek bir uzantı sunar. Eğer k 3 ise, örneğin:
sed 's/old/new/g3'
Bunlar istediğinizi yapmak için birleştirilebilir. İlk 3 oluşumunu değiştirmek için:
$ 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
burada \n
yararlıdır çünkü bunun asla bir çizgide gerçekleşmeyeceğinden emin olabiliriz.
Üç sed
ikame komutu kullanıyoruz:
s/\<old\>/\n/g4
Bu GNU dördüncü ve sonraki tüm tekrarlarını değiştirmek için old
birlikte \n
.
Genişletilmiş regex özelliği \<
, bir kelimenin başlangıcını eşleştirmek ve bir kelimenin \>
sonunu eşleştirmek için kullanılır. Bu, yalnızca eksiksiz kelimelerin eşleştiğini garanti eder. Genişletilmiş regex -E
seçeneği için gerektirir sed
.
s/\<old\>/new/g
Sadece ilk üç kalıntı old
kalıyor ve bu hepsini değiştiriyor new
.
s/\n/old/g
Dördüncü ve kalan tüm oluşumları old
ile değiştirilmiştir \n
birinci basamakta. Bu onları orijinal durumuna geri döndürür.
GNU sed Eğer mevcut değilse ve ilk 3 yerde değiştirmek istediğiniz old
için new
, daha sonra üç kullanın s
komutları:
$ 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
Bu k
, küçük bir sayı olduğunda iyi çalışır , ancak yetersiz bir şekilde ölçeklenir k
.
Bazı GNU dışı semenler komutları noktalı virgüllerle birleştirmeyi desteklemediğinden, buradaki her komut kendi -e
seçeneğiyle tanıtılır . Ayrıca, sed
sınır sembolleri kelimesini desteklediğinizi doğrulamak gerekebilir \<
ve \>
.
Sed'ye tüm dosyayı okumasını ve sonra değiştirmeleri gerçekleştirmesini söyleyebiliriz. Örneğin, old
bir BSD stili sed kullanmanın ilk üç örneğini değiştirmek için :
sed -E -e 'H;1h;$!d;x' -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'
Sed komutları H;1h;$!d;x
içindeki tüm dosyayı okur.
Yukarıdaki herhangi bir GNU uzantısı kullanmadığından, BSD (OSX) sed üzerinde çalışmalıdır. Dikkat edin, bu yaklaşımın sed
uzun çizgilerle başa çıkmak için bir yöntem gerektirdiğini düşünün. GNU iyi sed
olmalı. GNU dışı bir sürümü kullananlar, sed
uzun çizgilerle başa çıkabilme yeteneğini test etmelidir.
Bir GNU sed ile, daha da kullanabilirsiniz g
Yukarıda açıklanan trick, fakat \n
değiştirilir \x00
ilk üç tekrarlarını değiştirmek için,:
sed -E -e 'H;1h;$!d;x; s/\<old\>/\x00/g4; s/\<old\>/new/g; s/\x00/old/g'
Bu yaklaşım k
büyüdükçe ölçeklenir . Ancak, bunun \x00
orijinal dizginizde olmadığını varsayar . Karakteri \x00
bash dizgisine koymak imkansız olduğundan , bu genellikle güvenli bir varsayımdır.
tr '\n' '|' < input_file | sed …
. Ancak, elbette, bu girişin tamamını bir satıra çevirir ve bazı GNU dışı suter'lar keyfi olarak uzun satırları kaldıramaz. (2) “… yukarıda alıntılanan karakter dizisi '|'
herhangi bir karakter veya karakter dizisi ile değiştirilmelidir,” diyorsunuz tr
. (3) Son örneğinizde diyorsunuz -e 's/\<old\>/new/' -e 's/\<old\>/w/' | tr '\000' '\n'\>/new
. Bunun için bir yazım hatası gibi görünüyor -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/' | tr '\000' '\n'
.
Awk komutları, sözcüğün ilk N oluşumunu değiştirme ile değiştirmek için kullanılabilir.
Komutlar yalnızca sözcük tam bir eşleşme olduğunda değişecektir.
Aşağıdaki örneklerde, ilk 27
oluşumlarını old
ile değiştiriyorum.new
Alt kullanarak
awk '{for(i=1;i<=NF;i++){if(x<27&&$i=="old"){x++;sub("old","new",$i)}}}1' file
Bu komut, her alana denk gelene kadar
old
dolaşır, sayacın 27'nin altında olduğunu kontrol eder, artış yapar ve satırdaki ilk eşleşmenin yerini alır. Sonra bir sonraki alana / satıra ilerler ve tekrar eder.
Alanın manuel olarak değiştirilmesi
awk '{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file
Daha önce verilen komuta benzer, ancak zaten üzerinde olduğu alana bir işaretçi olduğu için
($i)
, alanın değerini ile arasındaki değeriold
değiştirirnew
.
Daha önce kontrol yapılması
awk '/old/&&x<27{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file
Çizginin eski ve sayının 27'nin altında olduğunu kontrol etmek,
SHOULD
bunlar yanlış olduğunda satırları işlemeyeceğinden küçük bir hız artışı sağlar.
SONUÇLAR
Örneğin
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
için
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
Bir dizenin sadece ilk üç örneğini değiştirmek istediğinizi söyleyin ...
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
not: Yukarıdakiler muhtemelen gömülü yorumlarla çalışmaz
... ya da benim örneğimde, '1' ...
22
211
211
311
Orada iki tane kayda değer teknik kullanıyorum. İlk olarak 1
, bir çizgideki her oluşum ile değiştirilir \n1
. Bu şekilde, daha sonra özyinelemeli değişimleri yaptığım için, yedek dizgimin değiştirme dizimi içermesi durumunda oluşumu iki kez değiştirmememden emin olabilirim . Örneğin, ben değiştirirseniz he
ile hey
bunun çalışmaya devam eder.
Bunu şöyle yapıyorum:
s/1/\
&/g
İkincisi, h
her olay için eski alana bir karakter ekleyerek değiştirmeleri sayıyorum . Üçe ulaştığımda, artık oluşmuyor. Bunu verilerinize uygularsanız ve \{3\}
istediğiniz toplam değiştirmeleri ve /\n1/
adresleri ne demek istediğinizi değiştirirseniz, yalnızca istediğiniz kadar değiştirmelisiniz.
Hepsini -e
okunabilirlik için yaptım . POSIXly Bu gibi yazılabilir:
nl='
'; sed "s/1/\\$nl&/g;:t${nl}/\n/{x;/.\{3\}/!{${nl}s/$/./;x;s/\n1/2/;bt$nl};x$nl};s/\n//g"
Ve w / GNU sed
:
sed 's/1/\n&/g;:t;/\n/{x;/.\{3\}/!{s/$/./;x;s/\n1/2/;bt};x};s/\n//g'
Ayrıca sed
, satır yönelimli olduğunu da unutmayın - dosyanın tamamını okumaz ve daha sonra diğer editörlerde olduğu gibi geri dönmeye çalışır. sed
basit ve verimli. Bu, aşağıdaki gibi bir şey yapmak için genellikle uygun olduğunu söyledi:
İşte basitçe çalıştırılan bir komut içine toplayan küçük bir kabuk işlevi:
firstn() { sed "s/$2/\
&/g;:t
/\n/{x
/.\{$(($1))"',\}/!{
s/$/./; x; s/\n'"$2/$3"'/
b t
};x
};s/\n//g'; }
Böylece bununla yapabilirim:
seq 11 100 311 | firstn 7 1 5
...ve Al...
55
555
255
311
...veya...
seq 10 1 25 | firstn 6 '\(.\)\([1-5]\)' '\15\2'
...almak...
10
151
152
153
154
155
16
17
18
19
20
251
22
23
24
25
... veya, örneğin eşleşmesi için (daha küçük bir siparişte) :
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
Perl'de kısa bir alternatif:
perl -pe 'BEGIN{$n=3} 1 while s/old/new/ && ++$i < $n' your_file
`$ N $ değerini istediğiniz gibi değiştirin.
Nasıl çalışır:
new
için old
( s/old/new/
) yerine koymaya çalışır ve ne zaman yapabilirse, $i
( ++$i
) değişkenini arttırır .1 while ...
daha azını yaptığı sürece ( ) hat üzerinde çalışmaya devam eder $n
ve o hat üzerinde en az bir tane oyuncu değişikliği yapabilir.Bir kabuk döngüsü kullanın ve ex
!
{ for i in {1..50}; do printf %s\\n '0/old/s//new/'; done; echo x;} | ex file.txt
Evet, biraz saçma.
;)
Not: old
Dosyada 50'den az örneği varsa bu başarısız olabilir . (Test etmedim.) Öyleyse, dosyayı değiştirilmemiş olarak bırakır.
Daha da iyisi, Vim kullanın.
vim file.txt
qqgg/old<CR>:s/old/new/<CR>q49@q
:x
Açıklama:
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
Basit ama çok hızlı olmayan bir çözüm, /programming/148451/how-to-use-sed-to-replace-only-the-first-occurrence-in-a 'da açıklanan komutların üzerinden geçmektir. -dosya
for i in $(seq 50) ; do sed -i -e "0,/oldword/s//newword/" file.txt ; done
Bu belirli sed komutu, muhtemelen yalnızca GNU sed için ve newword eski kelimenin bir parçası değilse çalışır . GNU olmayan sed için burada bir dosyadaki sadece ilk kalıbın nasıl değiştirileceğini görün.
GNU ile awk
kaydederken ayırıcı ayarlayabilirsiniz RS
için değiştirilecek kelime kelime sınırları ile sınırlanmış. Daha sonra k
, geri kalanlar için orijinal kayıt ayırıcıyı koruyarak, çıkıştaki kayıt ayırıcıyı, ilk kayıtlar için yedek kelimeye ayarlamak bir durumdur.
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, NR <= limit? replacement: RT}' file
VEYA
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, limit--? replacement: RT}' file