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:
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.
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 .
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) ).
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.