Sorun
for f in $(find .)
uyumsuz iki şeyi birleştirir.
find
yeni 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 find
tamamen çı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 cmd
Benzer problemlerin olduğunu unutmayın (orada boşluklar, yeni satır, tekli fiyat teklifi, çift fiyat teklifi ve ters eğik çizgi (ve xarg
geç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 find
kullanmaktır :zsh
IFS=$'\0'
IFS=$'\0'
for f in $(find . -print0)
(replace -print0
ile -exec printf '%s\0' {} +
için find
standart 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 something
birden 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 stdin
boru ya da -NH- grubudur /dev/null
.
Bunu kullanmak isteyebileceğiniz bir neden , paralel işlem için -P
GNU seçeneğini kullanmak olabilir xargs
. Bu stdin
sorun aynı zamanda GNU xargs
ile de çalışabilir ve -a
işlem ikamesini destekleyen mermilerle birlikte seçenek kullanılabilir:
xargs -r0n 20 -P 4 -a <(find . -print0) something
örneğin, something
her biri için 20 dosya argümanı içeren 4 eşzamanlı çağrı yürütmek .
İle zsh
ya da bash
, toplayıp çıkış döngü için başka bir yol find -print0
ile 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.4
ve üstü ayrıca aşağıdakiler find -print0
ile 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 find
ifadeyi, özyinelemeli globbing'in glob niteleyicileriyle kombinasyonuna çevirebilirsiniz . Örneğin, üzerinden döngü find . -name '*.txt' -type f -mtime -1
olacaktı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).
ksh93
ve bash
nihayetinde, **/
(ö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ış zsh
dosyası 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
( find
En azından GNU ) dizinleri açarak openat()
doğru O_NOFOLLOW
bayrakları kullanarak (desteklendiğinde) ve her dizin için bir dosya tanıtıcısını açık tutarak, zsh
/ bash
/ ksh
bunu 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 find
ile düzgün dizini inmek yapar -exec cmd {} \;
daha çok birlikte ve -exec cmd {} +
bir kez cmd
olarak örneğin yürütülür cmd ./foo/bar
veya cmd ./foo/bar ./foo/bar/baz
Zaman, cmd
kullanır ./foo/bar
özellikleridir bar
artık eşleşmesi kriterlere uygun olabilir find
, ancak daha da kötüsü, ./foo
olmuş 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 find
aramaya yetecek kadar dosyaları bekler cmd
).
Bazı find
uygulamaları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 -- bar
bazı uygulamalarda, dolayısıyla --
), bu nedenle ./foo
bir sembolik bağlantıda değişiklik yapma probleminden kaçınılır. Bu, rm
daha 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 find
eş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 cmd
bu yolda yapılan herhangi bir sistem çağrısı bir ENAMETOOLONG
hata 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 ENAMETOOLONG
hatayla karşılaşılması daha az olasıdır.
Bayt vs karakter
Ayrıca, find
genel 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é.txt
bu dosya adını karakter kümesinin ISO-8859-1 cєtщ.txt
olduğ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!
find
onun için metin olarak dosya isimleri göz önünde bu tür bir uygulama -name
/ -path
(benzeri ve daha yüklemler -iname
veya -regex
bazı uygulamaları ile).
Bunun anlamı, örneğin, birkaç find
uygulamayla (GNU dahil find
).
find . -name '*.txt'
Bizim bulmak olmaz 63 f4 74 e9 2e 74 78 74
olarak 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 {} \;
find
UTF-8'de kodlanmış 4 karakterden oluşan dosyaları başarıyla bulacaktır ancak dash
4 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ığı bash
veya zsh
nereye 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) ).
sh
FreeBSD 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 zsh
iç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)
+cmd
cmd
geç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 $REPLY
bir $reply
dizideki 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.