Bir dizindeki 10 milyondan fazla dosya üzerinde sed nasıl çalıştırılır?


16

İçinde 10144911 dosyaları olan bir dizin var. Şimdiye kadar aşağıdakileri denedim:

  • for f in ls; do sed -i -e 's/blah/blee/g' $f; done

Kabuğum lsçöktü, bir tilda içinde ama nasıl yapacağımı anlayamıyorum.

  • ls | xargs -0 sed -i -e 's/blah/blee/g'

İçin çok fazla argüman sed

  • find . -name "*.txt" -exec sed -i -e 's/blah/blee/g' {} \;

Artık bellek kalmadı

Bu tür komutun nasıl oluşturulacağı hakkında başka fikirleriniz mi var? Dosyaların birbirleriyle iletişim kurmasına gerek yoktur. ls | wc -l(çok yavaş) çalışıyor gibi görünüyor, bu yüzden mümkün olmalı.


1
sedHer dosya için çağırmaktan kaçınırsanız daha hızlı olur . Bir dizi dosyayı açmanın, düzenlemenin, kaydetmenin ve kapatmanın bir yolu olup olmadığından emin değilim sed; hız gerekliyse, belki perl veya python gibi farklı bir program kullanmak isteyebilirsiniz.
intuited

@intuited: Dosyalara hiçbir şey yapmamak daha da hızlı olurdu ... cidden? bir dosya kümesindeki bir deseni değiştirmek istiyorsanız, desen olup olmadığını görmek için her dosyaya bakmanız gerekir. 'bazı' dosyaları atlayabileceğinizi önceden biliyorsanız, dosyalara dokunmamak bile daha hızlıdır. ve başlangıç zamanı sedmuhtemelen daha hızlı başlatılması daha pythonya perlbunu yaparsanız hariç, hem de her şeyi o yorumlayıcı.
akira

@akira: Perl veya python'u bir komut satırına sığacak kadar çok dosya için bir kez başlatmanın, bu dosyaların her biri için sed'i bir kez başlatmaktan daha pahalı olduğunu mu söylüyorsunuz? Böyle olsaydı gerçekten şaşırırdım. —————— Sanırım benim önerimin düzenleme programını bir kez (en azından daha az kez - cevabımı görmek) çağırmak (başlatmak) olduğunu anlamadınız ve her dosyayı açmasını, değiştirmesini ve yeniden kaydetmesini sağladınız. sırayla, bu dosyaların her biri için ayrı ayrı düzenleme programını çağırmak yerine.
intuited

ilk yorumunuz gerçekten söylemek istediklerinizi yansıtmaz: "sed python / perl ile değiştirin" .. sadece bunu yaparak ve OP'nin verdiği komut satırına bakarak, masum bir okuyucu "find. -exec python" daha hızlı "bulmak. -exec sed" .. bu durum böyle değil. kendi cevabınızda aslında gerekli olandan daha sık python diyorsunuz.
akira

Akira'nın (sezgisel) önerinizi yanlış yorumladığını düşünüyorum. Dosyaları bir araya getirmeyi önerdiğine inanıyorum. Bunu xargs girişimimle denedim, tekrar denemek için zaman :)
Sandro

Yanıtlar:


19

Bunu deneyin:

find -name '*.txt' -print0 | xargs -0 -I {} -P 0 sed -i -e 's/blah/blee/g' {}

Her çağrı için yalnızca bir dosya adı besleyecektir sed. Bu "sed için çok fazla argüman" sorununu çözecektir. -PSeçeneği birden çok işlem aynı anda çatallı edilecek izin vermelidir. 0 işe yaramazsa (olabildiğince fazla çalıştırılması gerekiyorsa), sayıyı sınırlamak için diğer sayıları (10? 100? Sahip çekirdeklerin sayısı?) Deneyin.


3
Muhtemelen, find . -name \*.txt -print0kabuğun yerküreyi genişletmesinden ve bulmak için 10 milyon argüman için yer ayırmaya çalışmaktan kaçınması gerekecek .
Chris Johnsen

@ChrisJohnsen: Evet, doğru. Cevabımı göndererek acele ettim ve bu önemli parçaları da dahil ettim. Cevabımı bu düzeltmelerle düzenledim. Teşekkürler.
sonraki duyuruya kadar duraklatıldı.

Şimdi çalışıyor ... parmak çarpı
Sandro

7

Ben "merhaba 00000001" adlı " milyon 10000000" (ad başına 14 bayt ) adlı 10 milyon (boş) dosya üzerinde bu yöntemi (ve diğerleri) test ettim .

GÜNCELLEME: Şimdi yöntem üzerinde dört çekirdekli bir çalışma 'find |xargs'ekledim (hala 'sed' olmadan; sadece echo> / dev / null) ..

# Step 1. Build an array for 10 million files
#   * RAM usage approx:  1.5 GiB 
#   * Elapsed Time:  2 min 29 sec 
  names=( hello\ * )

# Step 2. Process the array.
#   * Elapsed Time:  7 min 43 sec
  for (( ix=0, cnt=${#names[@]} ; ix<$cnt; ix++ )) ; do echo "${names[ix]}" >/dev/null ; done  

Yukarıda verilen test verilerine göre çalıştırıldığında verilen cevapların nasıl bir özetini aşağıda bulabilirsiniz. Bu sonuçlar sadece temel genel giderleri içerir; yani 'sed' çağrılmadı. Sed süreci neredeyse kesinlikle en çok zaman alıcı olacak, ancak çıplak yöntemlerin nasıl karşılaştırıldığını görmenin ilginç olacağını düşündüm.

Dennis'in 'find |xargs'yöntem, tek bir çekirdek kullanarak, 21 dakika ** daha uzun * 4 saat sürdü bash arraybir üzerinde yönteme no sediçin çağrılan sed zaman vadede ... Ancak, 'Bul' tarafından sunulan çok çekirdekli avantajı gösterilen zaman farklılıkları daha önemli olmalıdır dosyalar işleniyor ...

           | Time    | RAM GiB | Per loop action(s). / The command line. / Notes
-----------+---------+---------+----------------------------------------------------- 
Dennis     | 271 min | 1.7 GiB | * echo FILENAME >/dev/null
Williamson   cores: 1x2.66 MHz | $ time find -name 'hello *' -print0 | xargs -0 -I {} echo >/dev/null {}
                               | Note: I'm very surprised at how long this took to run the 10 million file gauntlet
                               |       It started processing almost immediately (because of xargs I suppose),  
                               |       but it runs **significantly slower** than the only other working answer  
                               |       (again, probably because of xargs) , but if the multi-core feature works  
                               |       and I would think that it does, then it could make up the defecit in a 'sed' run.   
           |  76 min | 1.7 GiB | * echo FILENAME >/dev/null
             cores: 4x2.66 MHz | $ time find -name 'hello *' -print0 | xargs -0 -I {} -P 0 echo >/dev/null {}
                               |  
-----------+---------+---------+----------------------------------------------------- 
fred.bear  | 10m 12s | 1.5 GiB | * echo FILENAME >/dev/null
                               | $ time names=( hello\ * ) ; time for (( ix=0, cnt=${#names[@]} ; ix<$cnt; ix++ )) ; do echo "${names[ix]}" >/dev/null ; done
-----------+---------+---------+----------------------------------------------------- 
l0b0       | ?@#!!#  | 1.7 GiB | * echo FILENAME >/dev/null 
                               | $ time  while IFS= read -rd $'\0' path ; do echo "$path" >/dev/null ; done < <( find "$HOME/junkd" -type f -print0 )
                               | Note: It started processing filenames after 7 minutes.. at this point it  
                               |       started lots of disk thrashing.  'find' was using a lot of memory, 
                               |       but in its basic form, there was no obvious advantage... 
                               |       I pulled the plug after 20 minutes.. (my poor disk drive :(
-----------+---------+---------+----------------------------------------------------- 
intuited   | ?@#!!#  |         | * print line (to see when it actually starts processing, but it never got there!)
                               | $ ls -f hello * | xargs python -c '
                               |   import fileinput
                               |   for line in fileinput.input(inplace=True):
                               |       print line ' 
                               | Note: It failed at 11 min and approx 0.9 Gib
                               |       ERROR message: bash: /bin/ls: Argument list too long  
-----------+---------+---------+----------------------------------------------------- 
Reuben L.  | ?@#!!#  |         | * One var assignment per file
                               | $ ls | while read file; do x="$file" ; done 
                               | Note: It bombed out after 6min 44sec and approx 0.8 GiB
                               |       ERROR message: ls: memory exhausted
-----------+---------+---------+----------------------------------------------------- 

2

Tamamen güvenli bulma için başka bir fırsat :

while IFS= read -rd $'\0' path
do
    file_path="$(readlink -fn -- "$path"; echo x)"
    file_path="${file_path%x}"
    sed -i -e 's/blah/blee/g' -- "$file_path"
done < <( find "$absolute_dir_path" -type f -print0 )

1

Bu çoğunlukla konu dışı, ancak kullanabilirsiniz

find -maxdepth 1 -type f -name '*.txt' | xargs python -c '
import fileinput
for line in fileinput.input(inplace=True):
    print line.replace("blah", "blee"),
'

Buradaki ana fayda ... xargs ... -I {} ... sed ...hızdır: sed10 milyon kez çağırmaktan kaçınırsınız . Python kullanmaktan kaçınabilmeniz daha hızlı olurdu (python nispeten yavaş, nispeten), bu yüzden perl bu görev için daha iyi bir seçim olabilir. Eşdeğeri perl ile nasıl rahatça yapacağımdan emin değilim.

Bunun çalışma şekli, xargsPython'u tek bir komut satırına sığabilecek kadar çok argümanla çağıracak ve argümanları bitene kadar (bunlar tarafından sağlanır ls -f *.txt) yapmaya devam etmesidir . Her bir çağrının argümanlarının sayısı dosya adlarının uzunluğuna ve diğer bazı şeylere bağlı olacaktır. fileinput.inputFonksiyon her çağırma en argümanlar adlı dosyalardan ardışık satırları verir ve inplaceopsiyon sihirli "yakalamak" çıkışı ve her satırı değiştirmek için kullanın söyler.

Python'un dize replaceyönteminin regexps kullanmadığını unutmayın; eğer bunlara ihtiyacınız varsa, kullanmanız import reve kullanmanız gerekir print re.sub(line, "blah", "blee"). Bunlar, Perl-Uyumlu RegExps'tir ve bunlar, aldıklarınızın oldukça güçlendirilmiş versiyonlarıdır sed -r.

Düzenle

Akira yorumlarda belirtildiği gibi, komut ls -f *.txtyerine bir glob ( ) kullanan orijinal sürüm findçalışmaz çünkü globlar shell ( bash) tarafından işlenir . Bu, komut çalıştırılmadan önce, komut satırına 10 milyon dosya adının değiştirileceği anlamına gelir. Bu, bir komutun bağımsız değişken listesinin maksimum boyutunu aşacağı garantilidir. Bununla xargs --show-limitsilgili sisteme özgü bilgiler için kullanabilirsiniz .

Argüman listesinin maksimum boyutu da dikkate alınır xargs, bu da bu limite göre her bir python çağrısına geçirdiği argüman sayısını sınırlar. Yana xargshala Python oldukça birkaç kez çağırmak zorunda kalacak, akira önerisi kullanmak os.path.walkmuhtemelen kurtarmayacaksa biraz zaman listeleme dosyasını almak için.


1
glob operatörünü kullanmanın anlamı nedir (zaten bu kadar çok dosya için başarısız olacaktır) ... ve sonra dosyaları python'a besleyin os.path.walk()?
akira

@akira: Glob operatörü içeriğini değiştirmeye çalışırken kaçınmaktır .ve ... Kesinlikle bunu yapmanın başka yolları da var (yani find) ama OP'nin anladığı şeye olabildiğince yakın olmaya çalışıyorum. Kullanmamanın nedeni de budur os.path.walk.
intuited

@akira: İyi bir öneri olsa da, bu muhtemelen çok daha hızlı olacaktır.
intuited

OP'nin os.path.walkoldukça kolay anlayacağını düşünüyorum .
akira

0

Deneyin:

ls | while read file; do (something to $file); done

2
ls -fDaha iyi olurdu; Gerçekten stat()bu kadar dosya beklemek ve sıralamak istiyor musunuz?
geekosaur

şu anda çalışıyorum: f. * .txt için; falan yap; yapılır. Başarısız olursa ona bir darbe vereceğim. Teşekkür ederim!
Sandro
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.