Bash Komutundan Metin Dosyasının İçini Bulma ve Değiştirme


561

Belirli bir giriş dizesi için bul ve değiştir yapmanın , dosyada abcdiyelim XYZve başka bir dizeyle değiştirmenin en basit yolu nedir /tmp/file.txt?

Bir uygulama yazıyorum ve SSH aracılığıyla komutları yürütmek için IronPython kullanıyorum - ama Unix'i bu kadar iyi bilmiyorum ve ne arayacağımı bilmiyorum.

Bash'in komut satırı arayüzü olmanın yanı sıra çok güçlü bir betik dili olabileceğini duydum. Eğer bu doğruysa, böyle eylemler yapabileceğinizi varsayıyorum.

Bunu bash ile yapabilir miyim ve hedefime ulaşmak için en basit (tek satır) komut dosyası nedir?

Yanıtlar:


929

En kolay yol sed (veya perl) kullanmaktır:

sed -i -e 's/abc/XYZ/g' /tmp/file.txt

Hangi -iseçenek nedeniyle sed yerinde yerinde düzenleme yapmaya çağırır . Bu bash denilebilir.

Gerçekten sadece bash kullanmak istiyorsanız, aşağıdakiler işe yarayabilir:

while read a; do
    echo ${a//abc/XYZ}
done < /tmp/file.txt > /tmp/file.txt.t
mv /tmp/file.txt{.t,}

Bu, her satırın üzerinden geçer, bir değişiklik yapar ve geçici bir dosyaya yazılır (girişi hızlandırmak istemez). Sondaki taşıma sadece orijinal isme geçici olarak taşınır.


3
Dışında mv çağırmak sed gibi 'Bash olmayan' kadar. Neredeyse aynı yankıyı söyledim, ama bu bir kabuk yerleşik.
ince

5
Sed için -i argümanı Solaris için mevcut değildir (ve diğer bazı uygulamaları da düşünürüm), bunu aklınızda bulundurun. Bunu anlamaya birkaç dakika harcadım ...
Panky

2
Kendine not: düzenli ifade hakkında sed: s/..../..../ - Substituteve /g - Global
checksum

89
invalid command code CHata alan Mac kullanıcıları için not ... Yerinde değiştirme için BSD sed, belirtilen uzantıya -isahip bir yedekleme dosyasını kaydettiği için işaretlemeden sonra bir dosya uzantısı gerektirir . Örneğin: sed -i '.bak' 's/find/replace/' /file.txt Şu şekilde boş bir dize kullanarak yedeklemeyi atlayabilirsiniz:sed -i '' 's/find/replace/' /file.txt
Austin

8
İpucu: Büyük / küçük harfe duyarlı olmayan repalce kullanımı istiyorsanızs/abc/XYZ/gi
Boris D. Teoharov

166

Dosya işleme normalde Bash tarafından yapılmaz, ancak Bash tarafından çağrılan programlar tarafından örn.

perl -pi -e 's/abc/XYZ/g' /tmp/file.txt

-iBayrak yerinde değiştirme yapması gerektiğini söyler.

man perlrunOrijinal dosyanın nasıl yedekleneceği de dahil olmak üzere daha fazla ayrıntı için bkz .


37
İçimdeki saflık Perl'in sistemde mevcut olacağından emin olamayacağını söylüyor. Ancak günümüzde durum çok nadirdir. Belki de yaşımı gösteriyorum.
ince

3
Daha karmaşık bir örnek gösterebilir misiniz? "Chdir / blah" yerine "chdir / blah2" koymak gibi bir şey. Denedim perl -pi -e 's/chdir (?:\\/[\\w\\.\\-]+)+/chdir blah/g' text, ama ben -e satır 1 desen ve aşağıdaki kelime arasında boşluk olması ile bir hata almaya devam 1. Eşleşmeyen (regex; işaretli <- HERE m / (chdir) () ile işaretlenmiş (<- HERE ?: \\ / -e satır 1'de
CMCDragonkai

@CMCDragonkai Bu yanıtı kontrol edin: stackoverflow.com/a/12061491/2730528
Alfonso Santiago

69

Bunu tökezlediğimde şaşırdım ...

Paketle replacebirlikte gelen bir komut vardır "mysql-server", bu yüzden yüklediyseniz deneyin:

# replace string abc to XYZ in files
replace "abc" "XYZ" -- file.txt file2.txt file3.txt

# or pipe an echo to replace
echo "abcdef" |replace "abc" "XYZ"

Bununla man replaceilgili daha fazla bilgi için bakın .


12
Burada iki şey mümkündür: a) replaceyararlı bir bağımsız araçtır ve MySQL kullanıcıları ayrı olarak serbest bırakmalı ve ona bağlı olmalıdır b) replaceMySQL o_O biraz gerektirir Her iki durumda da, değiştirmek için mysql-server kurulumu yapmak yanlış bir şey olacaktır :)
Philip Whitehouse

sadece mac için mi çalışır? benim ubuntu bu komut mevcut değil centos
paul

1
Bunun nedeni mysql-serverpaketin kurulu olmamasıdır . @Rayro'nun işaret ettiği gibi, replacebunun bir parçasıdır.
Phius

2
"Uyarı: replace kullanımdan kaldırıldı ve gelecekteki bir sürümde kaldırılacak."
Steven Vachon

1
Windows'ta REPLACE komutunu çalıştırmamaya dikkat edin! Windows'da REPLACE komutu dosyaların hızlı bir şekilde çoğaltılması içindir. Bu tartışma ile ilgili değil.
Maor

53

Bu eski bir yazı ama @ centurian gibi değişkenleri kullanmak isteyen herkes için tek tırnak, hiçbir şeyin genişletilmeyeceği anlamına geliyor.

Değişkenleri almanın basit bir yolu, bu bash yan yana yerleştirme ile yapıldığından dize birleştirme yapmaktır:

sed -i -e "s/$var1/$var2/g" /tmp/file.txt

39

Bash, diğer kabuklar gibi, diğer komutları koordine etmek için kullanılan bir araçtır. Genellikle standart UNIX komutlarını kullanmaya çalışırsınız, ancak elbette kendi derlenmiş programlarınız, diğer kabuk komut dosyalarınız, Python ve Perl komut dosyalarınız vb. Dahil olmak üzere her şeyi çağırmak için Bash'i kullanabilirsiniz.

Bu durumda, bunu yapmanın birkaç yolu vardır.

Bir dosyayı okumak ve başka bir dosyaya yazmak, arama / değiştirme işlemini yapmak istiyorsanız sed'i kullanın:

sed 's/abc/XYZ/g' <infile >outfile

Dosyayı yerinde düzenlemek istiyorsanız (dosyayı bir düzenleyicide açma, düzenleme, ardından kaydetme) satır düzenleyicisine 'ex'

echo "%s/abc/XYZ/g
w
q
" | ex file

Ex tam ekran modu olmayan vi gibidir. Vi'nin ':' komut isteminde yapacağınız komutların aynısını verebilirsiniz.


33

Ben diğerleri arasında bu konu bulundu ve ben de benim ekleyerek ben en eksiksiz cevapları içeren katılıyorum:

  1. sedve edçok kullanışlı ... elle. @Johnny'deki şu koda bakın:

    sed -i -e 's/abc/XYZ/g' /tmp/file.txt
  2. Kısıtlamam bir kabuk betiğinde kullanmak olduğunda, "abc" veya "XYZ" yerine hiçbir değişken kullanılamaz. BashFAQ En azından anladığım yaramış. Bu yüzden kullanamıyorum:

    x='abc'
    y='XYZ'
    sed -i -e 's/$x/$y/g' /tmp/file.txt
    #or,
    sed -i -e "s/$x/$y/g" /tmp/file.txt

    ama ne yapabiliriz? @Johnny bir dediği gibi while read...ama maalesef bu hikayenin sonu değil. Aşağıdaki benimle iyi çalıştı:

    #edit user's virtual domain
    result=
    #if nullglob is set then, unset it temporarily
    is_nullglob=$( shopt -s | egrep -i '*nullglob' )
    if [[ is_nullglob ]]; then
       shopt -u nullglob
    fi
    while IFS= read -r line; do
       line="${line//'<servername>'/$server}"
       line="${line//'<serveralias>'/$alias}"
       line="${line//'<user>'/$user}"
       line="${line//'<group>'/$group}"
       result="$result""$line"'\n'
    done < $tmp
    echo -e $result > $tmp
    #if nullglob was set then, re-enable it
    if [[ is_nullglob ]]; then
       shopt -s nullglob
    fi
    #move user's virtual domain to Apache 2 domain directory
    ......
  3. O nullglobzaman ayarlanıp ayarlanmadığını görebileceğiniz gibi, içinde olduğu gibi bir dize olduğunda garip davranır *:

    <VirtualHost *:80>
     ServerName www.example.com

    hangisi olur

    <VirtualHost ServerName www.example.com

    bir uç köşeli ayraç yoktur ve Apache2 yüklenemez.

  4. Bu tür ayrıştırma, tek vuruşlu arama ve değiştirme işlemlerinden daha yavaş olmalıdır, ancak daha önce gördüğünüz gibi, bir ayrıştırma döngüsünde çalışan dört farklı arama modeli için dört değişken vardır.

Sorunun verilen varsayımları ile düşünebildiğim en uygun çözüm.


12
Senin (2) - yapabilirsin sed -e "s/$x/$y/", ve işe yarayacak. Çift tırnak değil. Değişkenlerdeki dizelerin kendileri özel anlam taşıyan karakterler içeriyorsa ciddi şekilde kafa karıştırıcı olabilir. Örneğin x = "/" veya x = "\" ise. Bu sorunları vurduğunuzda, muhtemelen bu iş için kabuğu kullanmaya çalışmayı bırakmanız gerektiği anlamına gelir.
ince

Merhaba ince, ben de Perl kullanmaya karşı olduğunu görüyorum. Çözümünüz nedir? Aslında bir dosyadaki bir yolu dinamik olarak değiştirmek istiyorum çünkü bu dizede çok fazla / var!
Mehdi

20

Şunları kullanabilirsiniz sed:

sed -i 's/abc/XYZ/gi' /tmp/file.txt

-iBulmak istediğiniz metnin abcveya ABCveya AbCvb. Olduğundan emin değilseniz "büyük / küçük harf yoksay" için kullanın .

Sen kullanabilirsiniz findve sedsize dosya adını bilmiyorsanız:

find ./ -type f -exec sed -i 's/abc/XYZ/gi' {} \;

Tüm Python dosyalarında bulun ve değiştirin:

find ./ -iname "*.py" -type f -exec sed -i 's/abc/XYZ/gi' {} \;

12

edKomutu dosya içi arama yapmak ve değiştirmek için de kullanabilirsiniz :

# delete all lines matching foobar 
ed -s test.txt <<< $'g/foobar/d\nw' 

" Dosyaları komut dosyaları ile düzenlemeed " bölümünde daha fazla bilgi bulabilirsiniz .


3
Bu çözüm GNU / FreeBSD (Mac OSX) uyumsuzluklarından (aksine sed -i <pattern> <filename>) bağımsızdır . Çok hoş!
Peterino

11

Üzerinde çalıştığınız dosya çok büyük değilse ve geçici olarak bir değişkente saklamak sorun değilse, tüm dosyada aynı anda Bash dizesi ikamesini kullanabilirsiniz - satır satır gitmenize gerek yoktur:

file_contents=$(</tmp/file.txt)
echo "${file_contents//abc/XYZ}" > /tmp/file.txt

Dosya içeriğinin tamamı, satır aralıkları dahil olmak üzere tek bir uzun dize olarak ele alınır.

XYZ bir değişken olabilir $replacementve burada sed kullanmamanın bir avantajı, arama veya değiştirme dizgisinin sed örüntüsü sınırlayıcı karakterini içerebileceğinden endişe duymanıza gerek olmamasıdır (genellikle, ancak zorunlu olarak /). Bir dezavantaj, düzenli ifadeleri veya sed'in daha karmaşık operasyonlarını kullanamamaktır.


Sekme karakterleriyle kullanmanın ipucu var mı? Nedense benim komut bu yöntem kaçan bir sürü ile sed değiştirdikten sonra sekmeleri ile hiçbir şey bulamaz.
Brian Hannay

2
Değiştirdiğiniz dizeye bir sekme koymak istiyorsanız, bunu Bash'in "dolarlık tek tırnak" sözdizimi ile yapabilirsiniz, böylece bir sekme $ '\ t' ile temsil edilir ve $ echo 'tab' $ yapabilirsiniz '\ t''sparated'> test dosyası; $ file_contents = $ (<testfile); $ echo "$ {file_contents // $ '\ t' / TAB}"; tabTABseparated `
johnraff


5

Aşağıdaki kabuk komutunu deneyin:

find ./  -type f -name "file*.txt" | xargs sed -i -e 's/abc/xyz/g'

4
Bu "yanlışlıkla tüm alt dizinlerdeki tüm dosyaları nasıl yaparım" için mükemmel bir yanıttır, ancak burada sorulan şey bu görünmüyor.
Üçlü

Bu sözdizimi BSD sürümünde çalışmaz sed, sed -i''bunun yerine kullanın.
kenorb

5

Dosyadaki metni etkileşimli olmayan şekilde düzenlemek için vim gibi yerinde metin düzenleyicisine ihtiyacınız vardır.

Komut satırından nasıl kullanılacağı basit bir örnek:

vim -esnc '%s/foo/bar/g|:wq' file.txt

Bu eşdeğerdir @slim cevap ait eski temelde aynı şeydir editörü.

İşte birkaç expratik örnek.

Metin Değiştirme fooile bardosyadaki:

ex -s +%s/foo/bar/ge -cwq file.txt

Birden çok dosya için arka boşlukları kaldırma:

ex +'bufdo!%s/\s\+$//e' -cxa *.txt

Sorun giderme (terminal sıkıştığında):

  • -V1Ayrıntılı mesajları göstermek için param ekleyin .
  • Kuvvet tarafından çıkın: -cwq!.

Ayrıca bakınız:


Değiştirmeleri etkileşimli yapmak istedim. Bu nedenle "vim -esnc '% s / foo / bar / gc |: wq' file.txt" dosyasını denedi. Ama terminal şimdi sıkışmış. Bash kabuğu garip davranmadan değiştirmeleri nasıl etkileşimli hale getirmeliyiz.
vineeshvs

Hata ayıklamak, eklemek -V1, bırakmaya zorlamak için kullanın wq!.
kenorb

2

Python'u bash betiğinde de kullanabilirsiniz. Buradaki en iyi yanıtlardan bazılarında çok başarılı olamadım ve bunu döngülere gerek kalmadan buldum:

#!/bin/bash
python
filetosearch = '/home/ubuntu/ip_table.txt'
texttoreplace = 'tcp443'
texttoinsert = 'udp1194'

s = open(filetosearch).read()
s = s.replace(texttoreplace, texttoinsert)
f = open(filetosearch, 'w')
f.write(s)
f.close()
quit()

1

Rpl komutunu kullanabilirsiniz. Örneğin, tüm php projesinde etki alanı adını değiştirmek istiyorsunuz.

rpl -ivRpd -x'.php' 'old.domain.name' 'new.domain.name' ./path_to_your_project_folder/  

Bu açık bir neden değil, ama çok hızlı ve kullanışlı. :)

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.