Sed - Dosyadaki bir kelimenin ilk k örneğini değiştirin


24

kBir 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.



Özellikle sed'e mi ihtiyacınız var veya başka araçlar kabul edilebilir mi? Komut satırında mı çalışmanız gerekiyor, yoksa bir metin editörü mü kabul edilebilir?
evilsoup

Komut satırında çalışan herhangi bir şey kabul edilebilir.
narendra-choudhary

Yanıtlar:


31

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.

Çizgi odaklı çözüm

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 \nyararlıdır çünkü bunun asla bir çizgide gerçekleşmeyeceğinden emin olabiliriz.

Açıklama:

Üç sedikame komutu kullanıyoruz:

  • s/\<old\>/\n/g4

    Bu GNU dördüncü ve sonraki tüm tekrarlarını değiştirmek için oldbirlikte \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 -Eseçeneği için gerektirir sed.

  • s/\<old\>/new/g

    Sadece ilk üç kalıntı oldkalıyor ve bu hepsini değiştiriyor new.

  • s/\n/old/g

    Dördüncü ve kalan tüm oluşumları oldile değiştirilmiştir \nbirinci basamakta. Bu onları orijinal durumuna geri döndürür.

GNU dışı çözüm

GNU sed Eğer mevcut değilse ve ilk 3 yerde değiştirmek istediğiniz oldiçin new, daha sonra üç kullanın skomutları:

$ 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 -eseçeneğiyle tanıtılır . Ayrıca, sedsınır sembolleri kelimesini desteklediğinizi doğrulamak gerekebilir \<ve \>.

Dosya odaklı çözüm

Sed'ye tüm dosyayı okumasını ve sonra değiştirmeleri gerçekleştirmesini söyleyebiliriz. Örneğin, oldbir 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;xiç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 seduzun çizgilerle başa çıkmak için bir yöntem gerektirdiğini düşünün. GNU iyi sedolmalı. GNU dışı bir sürümü kullananlar, seduzun çizgilerle başa çıkabilme yeteneğini test etmelidir.

Bir GNU sed ile, daha da kullanabilirsiniz gYukarıda açıklanan trick, fakat \ndeğiştirilir \x00ilk üç 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 kbüyüdükçe ölçeklenir . Ancak, bunun \x00orijinal dizginizde olmadığını varsayar . Karakteri \x00bash dizgisine koymak imkansız olduğundan , bu genellikle güvenli bir varsayımdır.


5
Bu yalnızca hatları için çalışır ve her satır ilk 4 oluşumları değişecek

1
@mikeserv Mükemmel fikir! Cevap güncellendi.
John1024,

(1) GNU ve GNU olmayan sed'den bahsettiniz ve öneriniz 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'.
G-Man

@ G-Man Çok teşekkürler! Cevabı güncelledim.
John1024

bu çok çirkin
Louis Maddox

8

Awk kullanımı

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 27oluşumlarını oldile 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 olddolaşı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ğeri olddeğiştirir new.

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, SHOULDbunlar 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

İlki (alt kullanarak), "eski" dizgisi * eski kelimesinden önce gelirse yanlış olanı yapar ; örneğin, “Yaşlı adama biraz altın ver.” → “Yaşlı adama biraz gnew ver.”
G-Man

@ G-Man unutmuşum Evet $ibit, onun düzenlendi, sayesinde :)

7

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' ...

ÇIKTI:

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 heile heybunun çalışmaya devam eder.

Bunu şöyle yapıyorum:

s/1/\
&/g

İkincisi, hher 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 -eokunabilirlik 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. sedbasit 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

4

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:

  • Her satır newiçin old( s/old/new/) yerine koymaya çalışır ve ne zaman yapabilirse, $i( ++$i) değişkenini arttırır .
  • Toplamda sübstitüsyonlardan 1 while ...daha azını yaptığı sürece ( ) hat üzerinde çalışmaya devam eder $nve o hat üzerinde en az bir tane oyuncu değişikliği yapabilir.

4

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: oldDosyada 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

: s // yeni <CR> boş bir regex son kullanılan arama yeniden kullanır çünkü, hem de çalışması gerekir
Eike

3

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.


"Old" u "kalın" ile değiştirmenin sorunlara neden olabileceğini belirlemek için +1
G-Man

2

GNU ile awkkaydederken ayırıcı ayarlayabilirsiniz RSiç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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.