Neden döngü bulgunun çıktısı kötü bir uygulama?


170

Bu soru ilham alıyor

Metni işlemek için neden bir kabuk halkası kullanılmıyor?

Bu yapıları görüyorum

for file in `find . -type f -name ...`; do smth with ${file}; done

ve

for dir in $(find . -type d -name ...); do smth with ${dir}; done

Bazı insanlar bu tür şeylerden neden kaçınılması gerektiğini açıklayan yazılara yorum yapmak için zaman ayırsalar bile burada neredeyse günlük olarak kullanılıyorlar ...
Bu yazıların sayısını görmek (ve bazen bu yorumların göz ardı edilmesi gerçeği) Bir soru sorabilirim diye düşündüm:

Döngüde neden findçıktı alıştırması perişan oluyor ve döndürülen her dosya adı / yolu için bir veya daha fazla komut çalıştırmanın doğru yolu findnedir?


12
Bunun "Asla çıkışını çözümlemeyin!" Gibi bir şey olduğunu düşünüyorum. - kesinlikle ikisini birden bire yapabilirsiniz, ancak bunlar üretim kalitesinden daha hızlı bir kesmek. Veya, daha genel olarak, kesinlikle asla dogmatik olma.
Bruce Ediger


Bu kanonik bir cevaba çevrilmelidir
Zaid

6
Çünkü bulmanın amacı bulduğu şeyin üzerinde dolaşmaktır.
OrangeDog

2
Bir yardımcı nokta - çıktıyı bir dosyaya göndermek ve daha sonra komut dosyasında işlemek isteyebilirsiniz. Bu şekilde, komut dosyasını hata ayıklamanız gerekirse dosya listesi incelenebilir.
user117529, 11:16

Yanıtlar:


87

Sorun

for f in $(find .)

uyumsuz iki şeyi birleştirir.

findyeni satır karakterleriyle sınırlandırılmış dosya yollarının listesini yazdırır. Bu $(find .)liste bağlamında işaretlenmemiş halde bıraktığınızda çağrılan split + glob işleci , karakterleri $IFS(varsayılan olarak newline içerir, ancak ayrıca boşluk ve sekme (ve NUL in zsh)) karakterlerine ayırır ve sonuçta elde edilen her kelimede globbing gerçekleştirir (hariç in zsh) (ve hatta ksh93 veya pdksh türevlerinde küme genleşmesi!).

Yapsanız bile:

IFS='
' # split on newline only
set -o noglob # disable glob (also disables brace expansion in pdksh
              # but not ksh93)
for f in $(find .) # invoke split+glob

Newline karakteri bir dosya yolundaki kadar geçerli olduğu için bu hala yanlış. Çıktısı find -print(bazı dolambaçlı hile kullanarak hariç sadece güvenilir sonrası işlenebilir değil burada gösterildiği gibi ).

Bu aynı zamanda, kabuğun findtamamen çıktısını depolaması gerektiği ve ardından dosyaların üzerinde dolaşmaya başlamadan önce bölme + bölme (bu çıktının bellekte ikinci kez depolanması anlamına gelir) anlamına gelir.

find . | xargs cmdBenzer problemlerin olduğunu unutmayın (orada boşluklar, yeni satır, tekli fiyat teklifi, çift fiyat teklifi ve ters eğik çizgi (ve xarggeçerli karakterlerin bir kısmını oluşturmayan bazı uygulamalarda baytlar) bir sorundur)

Daha doğru alternatifler

forÇıktısında bir döngü kullanmanın tek yolu, şunları destekleyen ve findkullanmaktır :zshIFS=$'\0'

IFS=$'\0'
for f in $(find . -print0)

(replace -print0ile -exec printf '%s\0' {} +için findstandart dışı (ama oldukça yaygın günümüzde) desteklemeyen uygulamalarda -print0).

Burada doğru ve taşınabilir yöntem kullanmaktır -exec:

find . -exec something with {} \;

Veya somethingbirden fazla argüman alabilir:

find . -exec something with {} +

Bir kabuk tarafından ele alınacak o dosya listesine ihtiyacınız varsa:

find . -exec sh -c '
  for file do
    something < "$file"
  done' find-sh {} +

(dikkat, birden fazla başlayabilir sh).

Bazı sistemlerde şunları kullanabilirsiniz:

find . -print0 | xargs -r0 something with

standart sözdizimi üzerinde çok az bir avantaja sahiptir ve demek olsa something'in stdinboru ya da -NH- grubudur /dev/null.

Bunu kullanmak isteyebileceğiniz bir neden , paralel işlem için -PGNU seçeneğini kullanmak olabilir xargs. Bu stdinsorun aynı zamanda GNU xargsile de çalışabilir ve -aişlem ikamesini destekleyen mermilerle birlikte seçenek kullanılabilir:

xargs -r0n 20 -P 4 -a <(find . -print0) something

örneğin, somethingher biri için 20 dosya argümanı içeren 4 eşzamanlı çağrı yürütmek .

İle zshya da bash, toplayıp çıkış döngü için başka bir yol find -print0ile geçerli:

while IFS= read -rd '' file <&3; do
  something "$file" 3<&-
done 3< <(find . -print0)

read -d '' newline ayrılmış kayıtları yerine NUL ayrılmış kayıtları okur.

bash-4.4ve üstü ayrıca aşağıdakiler find -print0ile bir dizede döndürülen dosyaları saklayabilir :

readarray -td '' files < <(find . -print0)

zsh(Koruma avantajına sahiptir eşdeğer find'in çıkış durumu):

files=(${(0)"$(find . -print0)"})

İle zshçoğu findifadeyi, özyinelemeli globbing'in glob niteleyicileriyle kombinasyonuna çevirebilirsiniz . Örneğin, üzerinden döngü find . -name '*.txt' -type f -mtime -1olacaktır:

for file (./**/*.txt(ND.m-1)) cmd $file

Veya

for file (**/*.txt(ND.m-1)) cmd -- $file

(ihtiyacı dikkat --gibi **/*, dosya yolları ile başlayan değildir ./, bu nedenle başlayabilir -örneğin).

ksh93ve bashnihayetinde, **/(özyinelemeli küreselleşme biçimlerinin daha fazla ilerlemesine neden olmamakla birlikte) için destek eklendi , ancak yine de burada **çok sınırlı kullanımını sağlayan küre niteleyicileri için değil . Ayrıca bash, 4.3'ten önce dizin ağacını alçalırken sembolik çizgileri takip ettiğine dikkat edin .

Üzerinde döngü için gibi $(find .)de bellekteki dosyaların tüm listeyi saklamak anlamına gelir, 1 . Bu, bazı durumlarda, dosyalar üzerindeki eylemlerinizin dosya bulma üzerinde bir etkisi olmasını istemiyorsanız (kendilerinin bulunmasına neden olabilecek daha fazla dosya eklediğinizde olduğu gibi) istenebilir .

Diğer güvenilirlik / güvenlik konuları

Yarış koşulları

Şimdi, güvenilirlikten bahsediyorsak, zaman find/ yarış zshdosyası arasındaki yarış koşullarından bahsetmek zorundayız ve kriterleri ve kullanıldığı süreyi karşıladığını kontrol etmek zorundayız ( TOCTOU yarışı ).

Bir dizin ağacını inerken bile, kişi bağları izlememeli ve TOCTOU yarışması olmadan bunu yapmalı. find( findEn azından GNU ) dizinleri açarak openat()doğru O_NOFOLLOWbayrakları kullanarak (desteklendiğinde) ve her dizin için bir dosya tanıtıcısını açık tutarak, zsh/ bash/ kshbunu yapmaz. Yani bir saldırganın dizini doğru zamanda bir link ile değiştirebilmesi karşısında, yanlış dizine inerek sona erebilir.

Bile findile düzgün dizini inmek yapar -exec cmd {} \;daha çok birlikte ve -exec cmd {} +bir kez cmdolarak örneğin yürütülür cmd ./foo/barveya cmd ./foo/bar ./foo/bar/bazZaman, cmdkullanır ./foo/barözellikleridir barartık eşleşmesi kriterlere uygun olabilir find, ancak daha da kötüsü, ./fooolmuş olabilir başka bir yerde sembolik bağ ile değiştirilir (ve yarış pencere ile daha büyük bir çok yapıldığı -exec {} +yerde findaramaya yetecek kadar dosyaları bekler cmd).

Bazı finduygulamaların -execdir, ikinci sorunu hafifletmek için (henüz standart olmayan) bir öngörüsü vardır.

İle:

find . -execdir cmd -- {} \;

find chdir()Çalıştırmadan önce dosyanın ana dizinine s cmd. Arama yapmak yerine cmd -- ./foo/barçağırır cmd -- ./bar( cmd -- barbazı uygulamalarda, dolayısıyla --), bu nedenle ./foobir sembolik bağlantıda değişiklik yapma probleminden kaçınılır. Bu, rmdaha güvenli gibi komutlar kullanılmasını sağlar (yine de farklı bir dosyayı kaldırabilir, ancak farklı bir dizindeki bir dosyayı kaldıramaz), ancak bağlantıları izlemeyecek şekilde tasarlanmadıkça dosyaları değiştirebilecek komutları kullanmaz.

-execdir cmd -- {} +bazen de çalışır, ancak GNU’nun bazı sürümlerini içeren çeşitli uygulamalarla buna findeşdeğerdir -execdir cmd -- {} \;.

-execdir Ayrıca, çok derin dizin ağaçları ile ilgili sorunların bazılarını çözme avantajına da sahiptir.

İçinde:

find . -exec cmd {} \;

Verilen yolun boyutu cmd, dosyanın bulunduğu dizinin derinliği ile artacaktır. Bu boyuttan daha büyük olursa PATH_MAX(Linux'ta 4k gibi bir şey), o zaman cmdbu yolda yapılan herhangi bir sistem çağrısı bir ENAMETOOLONGhata ile başarısız olacaktır .

İle -execdir, yalnızca (önceden eklenmiş ./) dosya adı iletilir cmd. Çoğu dosya sistemindeki dosya adlarının kendileri çok daha düşük bir sınıra ( NAME_MAX) sahiptir PATH_MAX, bu nedenle ENAMETOOLONGhatayla karşılaşılması daha az olasıdır.

Bayt vs karakter

Ayrıca, findgenel olarak dosya adlarının ele alınmasıyla ilgili güvenlik ve genel olarak güvenliği göz önüne alındığında genellikle göz ardı edilir , çoğu Unix benzeri sistemlerde, dosya adlarının bayt dizileri olduğu (dosya yolundaki 0 ​​bayt değeri ve çoğu sistem) ASCII tabanlı olanlar, şimdilik nadir olan EBCDIC tabanlı olanları görmezden geleceğiz (0x2f, yol sınırlayıcıdır).

Bu baytları metin olarak değerlendirmek isteyip istemediklerine karar vermek uygulamalara bağlıdır. Ve genellikle yaparlar, ancak genel olarak bayttan karakterlere çevirme, çevreye bağlı olarak kullanıcının yerel ayarına göre yapılır.

Bunun anlamı, belirli bir dosya adının yerel ayara bağlı olarak farklı metin gösterimlerine sahip olabileceğidir. Örneğin bayt dizisi 63 f4 74 e9 2e 74 78 74, côté.txtbu dosya adını karakter kümesinin ISO-8859-1 cєtщ.txtolduğu bir yerel ayarda ve karakter grubunun IS0-8859-5 olduğu bir yerel ayarda yorumladığı bir uygulama için olacaktır .

Daha da kötüsü. Karakter grubunun UTF-8 olduğu bir yerde (günümüzde norm), 63 f4 74 e9 2e 74 78 74 sadece karakterlerle eşleştirilemedi!

findonun için metin olarak dosya isimleri göz önünde bu tür bir uygulama -name/ -path(benzeri ve daha yüklemler -inameveya -regexbazı uygulamaları ile).

Bunun anlamı, örneğin, birkaç finduygulamayla (GNU dahil find).

find . -name '*.txt'

Bizim bulmak olmaz 63 f4 74 e9 2e 74 78 74olarak UTF-8 yerel ayarda çağırdığında yukarıdaki dosyayı *(0 veya daha fazla eşleşen karakterleri o olmayan karakterleri ulaşamasa değil bayt).

LC_ALL=C find... C yerel değeri karakter başına bir bayt ifade ettiğinden ve (genel olarak) tüm bayt değerlerinin bir karakterle eşleşmesini garanti eder (bazı bayt değerleri için muhtemelen tanımsız da olsa).

Şimdi bir kabuktan gelen bu dosya adları üzerinde döngü söz konusu olduğunda, bu byte vs karakteri de bir problem haline gelebilir. Genelde bu konuda 4 ana mermi türü görüyoruz:

  1. Hala çok baytlı olmayanlar gibi dash. Onlar için bir bayt bir karaktere eşlenir. Örneğin, UTF-8'de côté4 karakter, ancak 6 bayttır. UTF-8'in karakter kümesi olduğu bir yerel ayarda,

    find . -name '????' -exec dash -c '
      name=${1##*/}; echo "${#name}"' sh {} \;
    

    findUTF-8'de kodlanmış 4 karakterden oluşan dosyaları başarıyla bulacaktır ancak dash4 ile 24 arasında değişen uzunlukları rapor edecektir.

  2. yash: tam tersi. Sadece karakterlerle ilgileniyor . Aldığı tüm giriş dahili olarak karakterlere çevrilir. En tutarlı kabuğu oluşturur, ancak aynı zamanda rastgele bayt dizileriyle (geçerli karakterlere çeviri yapmayanlar) baş edemeyeceği anlamına da gelir. C yerelinde bile, 0x7f'nin üzerindeki bayt değerleriyle baş edemez.

    find . -exec yash -c 'echo "$1"' sh {} \;
    

    UTF-8 yerelinde, côté.txtörneğin daha önceki ISO-8859-1 sürümümüzde başarısız olacaktır .

  3. Multi-byte desteğinin hoşlandığı bashveya zshnereye gittiğini aşamalı olarak ekledi. Bunlar, sanki karaktermiş gibi karakterlerle eşleştirilemeyen baytları dikkate alır. Burada ve burada hala birkaç hata var. Özellikle GBK veya BIG5-HKSCS gibi daha az yaygın çok baytlık karakter karakterleriyle (çok baytlık karakterlerin çoğunun 0-127 aralığında bayt içermesi gibi (ASCII karakterleri gibi) ).

  4. shFreeBSD gibi olanlar (en az 11) veya mksh -o utf8-modeçoklu baytları destekleyenler ancak yalnızca UTF-8 için.

notlar

1 Bütünlük zshiçin, tüm listeyi belleğe kaydetmeden, özyinelemeli globbing kullanarak dosyalar üzerinde dolaşmanın zor bir yolundan söz edebiliriz:

process() {
  something with $REPLY
  false
}
: **/*(ND.m-1+process)

+cmdcmdgeçerli dosya yolundayken çağıran (genellikle bir işlev) bir glob niteleyicisidir $REPLY. İşlev, dosyanın seçilip seçilmeyeceğine karar vermek için true veya false değerini döndürür (ayrıca $REPLYbir $replydizideki birkaç dosyayı da değiştirebilir veya döndürebilir ). Burada bu fonksiyondaki işlemi yaparız ve yanlış seçerek dosyanın seçilmemesini sağlarız.


Eğer zsh ve bash mevcutsa, emniyetli bir şekilde davranmaya çalışmak yerine sadece globbing ve shell yapıları kullanarak daha iyi olabilirsinizfind . Global olarak bulma varsayılan olarak güvenli iken bulma varsayılan olarak güvenli değildir.
Kevin

@Kevin, düzenlemeye bakın.
Stéphane Chazelas

182

Neden üzerinden döngü findçıktı kötü alıştırma?

Basit cevap:

Çünkü dosya isimleri herhangi bir karakter içerebilir .

Bu nedenle, dosya adlarını sınırlandırmak için güvenle kullanabileceğiniz yazdırılabilir bir karakter yoktur.


Yeni satırlar genellikle dosya adlarını sınırlamak için kullanılır (çünkü dosya adlarına yeni satır karakterleri eklemek olağandışıdır) .

Bununla birlikte, yazılımınızı keyfi varsayımlar üzerine inşa ederseniz, en iyi ihtimalle olağandışı durumları ele almakta başarısız olursunuz ve en azından sisteminizi kontrol etmenizi sağlayan kötü niyetli istismarlara kendinizi açabilirsiniz. Bu yüzden sağlamlık ve güvenlik meselesi.

Yazılımı iki farklı şekilde yazabiliyorsanız, bunlardan biri kenar kasalarını (olağandışı girişleri) doğru şekilde kullanıyorsa, diğeri okunması daha kolaysa, bir tradeoff olduğunu iddia edebilirsiniz. (Yapmam. Doğru kodu tercih ederim.)

Kodun doğru, sağlam versiyonu Ancak, aynı zamanda kolay okunur, uç örnekleri üzerinde başarısız kod yazmadan için mazeret yoktur. Bu durum, findbulunan her bir dosya için bir komut çalıştırma ihtiyacı ile ilgilidir.


Daha spesifik olalım: Bir UNIX veya Linux sisteminde, dosya adları /(yol bileşeni ayırıcı olarak kullanılan) dışında herhangi bir karakter içerebilir ve boş bir bayt içermeyebilir.

Bu nedenle boş bir bayt, dosya adlarını sınırlandırmanın tek doğru yoludur.


GNU find, -print0yazdırdığı dosya adlarını sınırlandırmak için boş bir bayt kullanacak bir birincil içerdiğinden , GNU çıktısını işlemek için GNU ve bayrağıyla (ve bayrağını) güvenle find kullanabilir :xargs-0-rfind

find ... -print0 | xargs -r0 ...

Ancak, bu formu kullanmak için iyi bir sebep yoktur , çünkü:

  1. Orada olması gerekmeyen GNU bulgularına bir bağımlılık ekler ve
  2. findolduğu tasarlanmış bulduğu dosyalar üzerinde komutları çalıştırmak mümkün.

Ayrıca, GNU xargsgerektirir -0ve -rFreeBSD xargssadece gerektirir -0(ve hiçbir -rseçeneği yoktur ) ve bazıları xargshiç desteklemiyor -0. Bu yüzden sadece POSIX'in özelliklerine sadık kalmak find(bir sonraki bölüme bakınız) ve atlamak en iyisidir xargs.

2. noktaya gelince - findbulduğu dosyalar üzerinde komut çalıştırma yeteneği - bence Mike Loukides en iyisini söyledi:

find'nin işi ifadeleri değerlendiriyor - dosyaları bulmak değil. Evet, findkesinlikle dosyaları bulur; ama bu gerçekten sadece bir yan etki.

--Unix Elektrikli El Aletleri


POSIX’in belirtilen kullanımlarını find

Her bir findsonuç için bir veya daha fazla komut çalıştırmanın doğru yolu nedir ?

Bulunan her dosya için tek bir komut çalıştırmak için şunu kullanın:

find dirname ... -exec somecommand {} \;

Bulunan her dosya için sırayla birden fazla komut çalıştırmak için, ikinci komutun yalnızca ilk komut başarılı olursa çalıştırılması gerekir:

find dirname ... -exec somecommand {} \; -exec someothercommand {} \;

Aynı anda birden fazla dosyada tek bir komut çalıştırmak için:

find dirname ... -exec somecommand {} +

find ile bütünlüğünde sh

Çıktıyı yeniden yönlendirmek veya dosya uzantısından bir uzantı çıkarmak veya benzeri bir şey gibi komutta kabuk özelliklerini kullanmanız gerekirse, yapıyı kullanabilirsiniz sh -c. Bu konuda birkaç şey bilmelisin:

  • Hiçbir zaman gömmek {}doğrudan shkod. Bu, kötü amaçlı hazırlanmış dosya adlarından rasgele kod yürütülmesine izin verir. Ayrıca, POSIX tarafından bile çalışacağı belirtilmemiştir. (Bir sonraki noktaya bakınız.)

  • Kullanmayın {}birden çok kez ya da daha uzun bir tartışmanın parçası olarak kullanın. Bu taşınabilir değil. Örneğin, bunu yapma:

    find ... -exec cp {} somedir/{}.bak \;

    POSIX şartnamelerinifind alıntılamak için :

    Bir ederse utility_name veya argüman dizisi iki karakter "{}", ama sadece iki karakter içeriyor "{}", bu uygulama tanımlı olup olmadığıdır bulmak o iki karakter değiştirir veya değişiklik olmadan dizesini kullanır.

    ... "{}" iki karakterini içeren birden fazla argüman varsa, davranış belirtilmez.

  • Seçeneğe geçirilen kabuk komut dizesini izleyen argümanlar -c, ile başlayan$0 kabuğun konum parametrelerine ayarlanır . İle başlamıyor $1.

    Bu nedenle, yumurtlanan kabuğun içinden hata raporlamada kullanılacak $0gibi "sahte" bir değer eklemek iyidir find-sh. Ayrıca, bu, "$@"çok sayıda dosyayı kabuğa geçirirken olduğu gibi yapıların kullanılmasına izin verirken, bunun için bir değerin çıkarılması, $0geçirilen ilk dosyanın ayarlanacağı $0ve dolayısıyla dahil edilmeyeceği anlamına gelir "$@".


Dosya başına tek bir kabuk komutu çalıştırmak için şunu kullanın:

find dirname ... -exec sh -c 'somecommandwith "$1"' find-sh {} \;

Ancak, bir kabuk döngüsündeki dosyaları işlemek genellikle daha iyi performans sağlar, böylece bulunan her bir dosya için bir kabuk oluşturmazsınız:

find dirname ... -exec sh -c 'for f do somecommandwith "$f"; done' find-sh {} +

( Konum parametrelerinin her birine for f doeşdeğer olduğunu for f in "$@"; dove bunları işlediğini unutmayın; başka bir deyişle, findadlarındaki özel karakterlerden bağımsız olarak, bulduğu her bir dosyayı kullanır .)


Doğru findkullanım için diğer örnekler :

(Not: Bu listeyi genişletmekten çekinmeyin.)


5
Ayrıştırma findçıktısına bir alternatif bilmediğim bir durum var - her bir dosya için komutları geçerli kabukta (örneğin değişkenler ayarlamak istediğiniz için) çalıştırmanız gerekir . Bu durumda, while IFS= read -r -u3 -d '' file; do ... done 3< <(find ... -print0)bildiğim en iyi deyimdir. Notlar: <( )taşınabilir değildir - bash veya zsh kullanın. Ayrıca, -u3ve 3<döngü içinde bir şey stdin okumaya çalışır durumda ve oradalar.
Gordon Davisson

1
@GordonDavisson belki-ama bu değişkenleri ayarlamak için ne gerekiyor için ? Neyin olursa olsun , görüşme içinde ele alınması gerektiğini savunuyorum find ... -exec. Ya da kullanım çantanıza bakarsa, sadece bir kabuk küre kullanın.
Wildcard

1
Dosyaları işledikten sonra sık sık bir özet yazdırmak istiyorum ("2 dönüştürülmüş, 3 atlandı, aşağıdaki dosyalar hata yaptı: ...") ve bu sayımların / listelerin kabuk değişkenlerinde birikmesi gerekiyor. Ayrıca, sırayla yinelemeden daha karmaşık şeyler yapabilmem için bir dosya adı dizisi oluşturmak istediğim durumlar var (bu durumda filelist=(); while ... do filelist+=("$file"); done ...).
Gordon Davisson

3
Cevabınız doğru. Ancak dogmayı sevmiyorum. Daha iyisini bilmeme rağmen, findçıktı üzerine döngü yazmanın güvenli ve daha kolay olduğu birçok (özel olarak etkileşimli) kullanım durumu vardır, hatta kullanımı daha da kötüleştirir ls. Bunu her gün problemsiz yapıyorum. -Print0, - null, -z veya -0 her türlü alet seçeneklerini biliyorum. Ancak, gerçekten gerekmedikçe bunları etkileşimli kabuk istemimde kullanmak için zaman harcamam. Bu, cevabınızda da belirtilebilir.
rudimeier

16
@ rudimeier, dogma ile ilgili en iyi uygulama argümanı ölümle sonuçlandı . İlgilenmiyorum. Eğer etkileşimli kullanırsanız ve işe yararsa, gayet iyi, sizin için iyi - ama bunu yapmayı teşvik etmeyeceğim. Sağlam kodun ne olduğunu öğrenmeye can atan senaryo yazarlarının yüzdesi ve sonra bunu yalnızca etkileşimli olarak yapmaya alıştıkları şeyi yapmak yerine üretim komut dosyaları yazarken yaparsınız . İşlem, her zaman en iyi uygulamaları teşvik etmektir. İnsanların işleri yapmanın doğru bir yolu
Wildcard

10

Bu cevap çok büyük sonuç içindir, örneğin yavaş bir ağ üzerinden dosyaların bir listesini alırken, esas olarak performans ile ilgilidir. Az miktarda dosya için (yerel diskte birkaç 100 veya belki de 1000 bile diyorsunuz) bunun çoğu tartışmalı.

Paralellik ve hafıza kullanımı

Verilen diğer cevapların yanı sıra, ayrılık sorunları ve bununla ilgili olarak,

for file in `find . -type f -name ...`; do smth with ${file}; done

Geri tepme çubuklarının içindeki bölüm, hat çizgileri üzerinde bölünmeden önce ilk önce tam olarak değerlendirilmelidir. Bu, eğer çok miktarda dosya alırsanız, çeşitli bileşenlerde ne boyutta bir boyutta olduğunu boğabilir; sınır yoksa, hafızanız tükenebilir; ve her durumda ilk listeyi bile yayınlamadan önce listenin tamamı çıkarılan findve ayrıştırılana kadar beklemeniz forgerekir smth.

Tercih edilen unix yolu, içsel olarak paralel çalışan ve genel olarak keyfi olarak büyük tamponlara ihtiyaç duymayan borularla çalışmaktır. Bu şu anlama gelir: Sizinle findparalel olarak çalışmayı tercih edersiniz smthve sadece geçerli dosya ismini RAM'a verirken saklarsınız smth.

Bunun için en azından kısmen OKish çözümünün adı geçen find -exec smth. Tüm dosya adlarını bellekte tutma ihtiyacını ortadan kaldırır ve güzel bir şekilde paralel çalışır. Ne yazık ki, aynı zamanda smthdosya başına bir işlem başlatır . Eğer smthsadece tek bir dosya üzerinde çalışabilir, o öyle olmak zorunda yoludur.

Mümkünse en iyi çözüm find -print0 | smth, smthdosya adlarını STDIN'inde işleyebilmesi ile mümkün olacaktır. O zaman, smthne kadar dosya olursa olsun, yalnızca bir işleminiz vardır ve iki işlem arasında yalnızca küçük bir bayt aralığını (içsel boru tamponlaması ne olursa olsun) tamponlamanız gerekir. Tabii ki, smthstandart bir Unix / POSIX komutu ise bu oldukça gerçekçi değil , fakat kendiniz yazıyorsanız bir yaklaşım olabilir.

Bu mümkün değilse, o zaman find -print0 | xargs -0 smthmuhtemelen, daha iyi çözümlerden biri. @ Dave_thompson_085 yorumlarda belirtildiği gibi, xargsbirden çok kez çalıştırılmasının genelinde argümanları bölmek yok smth(128 KB ya da her türlü sınırı ile dayatılan aralığında, varsayılan olarak sistem limitlere ulaşıldığında execsistem üzerinde) ve kaç etkilemek için seçenekler vardır dosyalar bir çağrıya verilir smth, bu nedenle smthişlem sayısı ile ilk gecikme arasında bir denge bulunur .

EDIT: "en iyi" kavramlarını ortadan kaldırdı - daha iyi bir şeyin ortaya çıkıp çıkmayacağını söylemek zor. ;)


find ... -exec smth {} +çözümdür.
Joker,

find -print0 | xargs smthhiç çalışmaz, ancak find -print0 | xargs -0 smth(not -0) ya find | xargs smthda dosya adlarında beyaz boşluk yoksa ya da ters eğik çizgi smth, mevcut ve tek bir argüman listesine sığdırabilecek kadar çok sayıda dosya adına sahipse çalışır ; Maxargs'ı aşarsanız, smthverilen tüm artaları işlemek için gerektiği kadar çalışır (limitsiz). İle daha küçük 'parçalar' (yani biraz daha erken paralellik) ayarlayabilirsiniz -L/--max-lines -n/--max-args -s/--max-chars.
dave_thompson_085


4

Bunun bir nedeni, boşlukların çalışmalara bir anahtar atarak, 'foo bar' dosyasını 'foo' ve 'bar' olarak değerlendirmesini sağlamaktır.

$ ls -l
-rw-rw-r-- 1 ec2-user ec2-user 0 Nov  7 18:24 foo bar
$ for file in `find . -type f` ; do echo filename $file ; done
filename ./foo
filename bar
$

-Exec yerine kullanılırsa tamam çalışır

$ find . -type f -exec echo filename {} \;
filename ./foo bar
$ find . -type f -exec stat {} \;
  File: ‘./foo bar’
  Size: 0               Blocks: 0          IO Block: 4096   regular empty file
Device: ca01h/51713d    Inode: 9109        Links: 1
Access: (0664/-rw-rw-r--)  Uid: (  500/ec2-user)   Gid: (  500/ec2-user)
Access: 2016-11-07 18:24:42.027554752 +0000
Modify: 2016-11-07 18:24:42.027554752 +0000
Change: 2016-11-07 18:24:42.027554752 +0000
 Birth: -
$

Özellikle de findher dosya için bir komut çalıştırma seçeneği olduğu için kolayca en iyi seçenek budur.
Centimane

1
Ayrıca -exec ... {} \;karşı-exec ... {} +
düşünün

1
kullanırsanız for file in "$(find . -type f)" ve echo "${file}"sonra boşluklarla bile çalışırsa, sanırım diğer özel karakterler daha fazla soruna neden olur
mazs

9
@mazs - hayır, alıntı yapmak düşündüğünüzü yapmaz. Birkaç dosya içeren bir dizinde, for file in "$(find . -type f)";do printf '%s %s\n' name: "${file}";donehangisinin (size göre) önce basılacağı ayrı bir satıra yazdırılması gerektiğini deneyin name:. Öyle değil.
don_crissti

2

Herhangi bir komutun çıktısı tek bir dize olduğundan, ancak döngünüzün dönmesi için bir dizi dizeye ihtiyacı vardır. İşe yaramasının nedeni, mermilerin ihanetle sizin için beyaz alandaki ipi ayırmasıdır.

İkincisi, belirli bir özelliğe ihtiyaç duymazsanız, findkabuğunuzun muhtemelen özyinelemeli bir glob desenini kendi başına genişletebileceğini ve en önemlisi uygun bir diziye genişleyeceğini unutmayın.

Bash örneği:

shopt -s nullglob globstar
for i in **
do
    echo «"$i"»
done

Balıkta Aynı:

for i in **
    echo «$i»
end

Özelliklerine ihtiyacınız varsa find, sadece NUL'a (örneğin, find -print0 | xargs -r0deyim) ayırdığınızdan emin olun .

Balık, NUL ayrılmış çıktısını yineleyebilir. Yani bu aslında fena değil :

find -print0 | while read -z i
    echo «$i»
end

Son küçük bir kaçıklık olarak, birçok kabukta (tabii ki Fish değil), komut çıktısı üzerinden döngü, döngü gövdesini bir alt kabuk (yani döngü sona erdikten sonra görünen herhangi bir şekilde değişken ayarlayamazsınız) yapacaktır. asla istediğini yapma.


@ don_crissti Kesinlikle. O değil genel olarak çalışırlar. "İşe yaradığını" (tırnak işaretleri ile) söyleyerek alaycı olmaya çalışıyordum.
user2394284 12:16

Özyinelemeli küreselleşmenin zsh90'lı yılların başlarında ortaya çıktığını unutmayın (buna ihtiyacınız **/*olsa da). fishbash'nin eşdeğer özelliğinin daha önceki uygulamaları gibi dizin ağacını alçalırken sembolik izleri izler. Uygulamalar arasındaki farklar için ls *, ls ** ve ls *** sonuçlarına bakınız .
Stéphane Chazelas

1

Bulgunun çıktısı üzerinde dolaşmak kötü bir uygulama değildir - kötü uygulama (bu ve tüm durumlarda), girdilerinizin belirli bir biçim olduğunu bilmek (test etme ve onaylama) yerine belirli bir biçim olduğunu varsaymaktır .

TLDR / cbf: find | parallel stuff

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.