Birden fazla dosyayı değiştirme


194

Aşağıdaki komut 2 dosyanın içeriğini doğru bir şekilde değiştirmektedir.

sed -i 's/abc/xyz/g' xaa1 xab1 

Ama yapmam gereken bu tür dosyaları dinamik olarak değiştirmek ve dosya adlarını bilmiyorum. İle başlayan xa*ve seddosya içeriğini değiştirmek gerekir geçerli dizindeki tüm dosyaları okuyacak bir komut yazmak istiyorum .


63
Yani sed -i 's/abc/xyz/g' xa*?
Paul R

3
Buradaki cevaplar yeterli değil. Bkz. Unix.stackexchange.com/questions/112023/…
Isaac

Yanıtlar:


136

Daha iyisi:

for i in xa*; do
    sed -i 's/asd/dfg/g' $i
done

çünkü kimse kaç dosya olduğunu bilmiyor ve komut satırı sınırlarını aşmak kolay.

Çok fazla dosya olduğunda şunlar olur:

# grep -c aaa *
-bash: /bin/grep: Argument list too long
# for i in *; do grep -c aaa $i; done
0
... (output skipped)
#

18
Bu kadar çok dosya varsa, komutta komut satırı sınırını forkıracaksınız. Kendinizi bundan korumak için kullanmak zorundasınızfind ... | xargs ...
Glenn Jackson

1
Ben uygulama bilmiyorum, ama "xa *" desen bir noktada genişletilmesi gerekiyor. Kabuk, genişlemeyi forolduğundan daha farklı yapıyor mu echoveya grep?
glenn jackman

4
güncellenmiş cevaba bakınız. Daha fazla bilgiye ihtiyacınız varsa, lütfen resmi bir soru sorun, böylece insanlar size yardımcı olabilir.
lenik

5
Sed komutu, kullanmak gerekir "$i"yerine $iboşluklarla dosya adları üzerinde önlemek kelime yarılmasına. Aksi takdirde bu çok güzel.
Joker

4
Liste ile ilgili olarak, farkın, forsadece bir yerleşik bile değil, dil sözdiziminin bir parçası olduğuna inanıyorum . Çünkü sed -i 's/old/new' *, genişletilmesi *TÜMÜNÜ sed için bir argüman olarak geçirilmeli ve sedişlemin başlatılmadan önce bunun olması gerektiğinden oldukça eminim . forDöngüyü kullanarak, tam arglist (genişlemesi *) hiçbir zaman komut olarak geçirilmez, yalnızca kabuk belleğinde saklanır ve yinelenir. Bununla ilgili herhangi bir referansım yok, sadece fark var gibi görünüyor. (Daha bilgili birinden duymak isterim ...)
Wildcard

167

Kimse bulmak için -exec argüman bahsetmedi şaşırdım, bu tür bir kullanım-case için tasarlanmıştır, ancak her eşleşen dosya adı için bir işlem başlatacak:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} \;

Alternatif olarak, daha az işlemi çağıracak xargs kullanılabilir:

find . -type f -name 'xa*' | xargs sed -i 's/asd/dsg/g'

Ya da daha çok bulmanın alt işlem çağrısı başına birden fazla dosya sağlamasına izin vermek için find yerine + exec değişkenini kullanın ;:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} +

8
Bu cevaptaki komutu bu şekilde değiştirmek zorunda kaldım: find ./ -type f -name 'xa*' -exec sed -i '' 's/asd/dsg/g' {} \;bu, find komutunun konumu ./ve -iOSX için tek bir çift tırnak .
shelbydz

Find komutu ealfonso tarafından sağlandığı gibi çalışır , sadece backupsuffix parametresine ./eşittir .ve sonradır -i.
uhausbrand

Bulma -execseçeneği {} +, sorunu belirtildiği gibi çözmek için yeterlidir ve çoğu gereksinim için iyi olmalıdır. Ancak xargsgenel olarak daha iyi bir seçimdir çünkü seçenekle paralel işlemeye de izin verir -p. Glob genişlemeniz komut satırı uzunluğunuzu aşacak kadar büyük olduğunda, sıralı bir çalıştırmada bir hızlandırmadan da yararlanabilirsiniz.
Amit Naidu

78

Birlikte grep ve sed kullanabilirsiniz. Bu, alt dizinleri özyinelemeli olarak aramanızı sağlar.

Linux: grep -r -l <old> * | xargs sed -i 's/<old>/<new>/g'
OS X: grep -r -l <old> * | xargs sed -i '' 's/<old>/<new>/g'

For grep:
    -r recursively searches subdirectories 
    -l prints file names that contain matches
For sed:
    -i extension (Note: An argument needs to be provided on OS X)

3
Benim için bu yöntemin bonusu ben grep -vgit klasörleri önlemek için bir kayma oldugrep -rl <old> . | grep -v \.git | xargs sed -i 's/<old>/<new>/g'
Martin Lyne

Mac için en iyi çözüm!
Marquis Blount

30

Bu komutlar sedMac OS X ile birlikte gelen varsayılan ayarlarda çalışmaz .

Gönderen man 1 sed:

-i extension
             Edit files in-place, saving backups with the specified
             extension.  If a zero-length extension is given, no backup 
             will be saved.  It is not recommended to give a zero-length
             extension when in-place editing files, as you risk corruption
             or partial content in situations where disk space is exhausted, etc.

Denenmiş

sed -i '.bak' 's/old/new/g' logfile*

ve

for i in logfile*; do sed -i '.bak' 's/old/new/g' $i; done

Her ikisi de iyi çalışıyor.


2
@Sumek OS X'te tüm olayların yerini alan sed'i gösteren örnek bir terminal oturumu: GitHub Gist
funroll

Bunu tüm web sitesi yapılandırma dosyalarımdaki iki farklı satırı aşağıdaki bir astarla değiştirmek için kullandım. sed -i.bak "s / supercache_proxy_config / proxy_includes \ / supercache_config / g; s / basic_proxy_config / proxy_include \ / basic_proxy_config / g" sites-available / * Dosya için işiniz bittiğinde * .bak dosyalarını silmeyi unutmayın sistem hijyeni uğruna.
Josiah

19

@PaulR bunu bir yorum olarak gönderdi, ancak insanlar bunu bir cevap olarak görmelidir (ve bu cevap ihtiyaçlarım için en iyi şekilde çalışır):

sed -i 's/abc/xyz/g' xa*

Bu, muhtemelen onlarca sırada, ancak milyonlarca sırada değil, ılımlı miktarda dosya için çalışacaktır .


Değiştirmelerinizde eğik çizgiler olduğunu varsayın. Dosyayollarıyla başka bir örnek sed -i 's|auth-user-pass nordvpn.txt|auth-user-pass /etc/openvpn/nordvpn.txt|g' *.ovpn.
Léo Léopold Hertz 준영

10

Bir başka çok yönlü yol da kullanmaktır find:

sed -i 's/asd/dsg/g' $(find . -type f -name 'xa*')

1
bu find komutunun çıktısı genişletilir, bu nedenle bu sorunu gidermez. Bunun yerine
-exec

@erjoalgo bu, sed komutunun birden çok girdi dosyasını işleyebilmesi nedeniyle çalışır. Find komutunun genişletilmesi tam olarak çalışması için gerekliydi.
dkinzer

dosya sayısı komut satırı sınırlarına girmediği sürece çalışır.
ealfonso

Bu sınır yalnızca makinenin kullanabileceği bellek kaynaklarına bağlıdır ve yürütme sınırıyla tamamen aynıdır.
dkinzer

4
Bu doğru değil. Yukarıdaki komutunuzda, $ (find. ...) tek bir komutta genişletilir ve eşleşen birçok dosya varsa çok uzun olabilir. Çok uzunsa (örneğin sistemimde sınır 2097152 karakter civarındaysa) bir hata alabilirsiniz: "Bağımsız değişken listesi çok uzun" ve komut başarısız olur. Bu konuda biraz bilgi almak için lütfen bu hatayı google.
ealfonso

2

findBenzer bir görev için kullanıyorum . Oldukça basit: bunun için bir argüman olarak geçmelisiniz sed:

sed -i 's/EXPRESSION/REPLACEMENT/g' `find -name "FILE.REGEX"`

Bu şekilde karmaşık döngüler yazmak zorunda kalmazsınız ve hangi dosyaları değiştireceğinizi görmek, çalıştırmadan findönce çalıştırmak kolaydır sed.


1
Bu @ dkinzer'in cevabı ile tamamen aynı .
Bay Tao

0

yapabilirsin

' xxxx ' metni arar ve ' yyyy ' ile değiştirir

grep -Rn '**xxxx**' /path | awk -F: '{print $1}' | xargs sed -i 's/**xxxx**/**yyyy**/'

0

Bir komut dosyası çalıştırabiliyorsanız, benzer bir durum için yaptığım şey:

Bir sözlük / hashMap (ilişkilendirilebilir dizi) ve sedkomut için değişkenler kullanarak, birkaç dizeyi değiştirmek için dizi arasında döngü yapabiliriz. Joker karakter name_patterneklenmesi name_pattern='File*.txt', belirli bir dizindeki ( source_dir) bir desenle (bu gibi bir şey olabilir ) dosyalarda yerinde değiştirmeye izin verecektir . Tüm değişiklikler yazılır logfileiçindedestin_dir

#!/bin/bash
source_dir=source_path
destin_dir=destin_path
logfile='sedOutput.txt'
name_pattern='File.txt'

echo "--Begin $(date)--" | tee -a $destin_dir/$logfile
echo "Source_DIR=$source_dir destin_DIR=$destin_dir "

declare -A pairs=( 
    ['WHAT1']='FOR1'
    ['OTHER_string_to replace']='string replaced'
)

for i in "${!pairs[@]}"; do
    j=${pairs[$i]}
    echo "[$i]=$j"
    replace_what=$i
    replace_for=$j
    echo " "
    echo "Replace: $replace_what for: $replace_for"
    find $source_dir -name $name_pattern | xargs sed -i "s/$replace_what/$replace_for/g" 
    find $source_dir -name $name_pattern | xargs -I{} grep -n "$replace_for" {} /dev/null | tee -a $destin_dir/$logfile
done

echo " "
echo "----End $(date)---" | tee -a $destin_dir/$logfile

Birincisi, çiftleri dizisi bildirildi, her bir çift daha sonra, bir yedek dizesi WHAT1için değiştirilecek FOR1ve OTHER_string_to replaceiçin değiştirilecektir string replaceddosyada File.txt. Döngüde dizi okunur, çiftin ilk üyesi olarak replace_what=$ive ikincisi olarak alınır replace_for=$j. findDizinde komut arar dosya adı (yani bir joker içerebilir) ve sed -idaha önce tanımlanan ne aynı dosya (lar) komut cümledeki. Sonunda birgrep , dosyalarda yapılan değişiklikleri günlüğe kaydetmek için günlük dosyasına yeniden yönlendirme .

Bu benim için çalıştı GNU Bash 4.3 sed 4.2.2ve VasyaNovikov'un bash'daki tuples üzerine Döngü için verdiği cevaba dayanıyordu .

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.