समस्या
for f in $(find .)
दो असंगत चीजों को जोड़ती है।
find
न्यूलाइन वर्णों द्वारा सीमांकित फ़ाइल पथों की सूची प्रिंट करता है। जबकि स्प्लिट + ग्लोब ऑपरेटर को जब आप छोड़ते हैं $(find .)
, तो उस सूची के संदर्भ में यह अछूता रहता है कि यह इसके वर्णों पर विभाजित होता है $IFS
(डिफ़ॉल्ट रूप से इसमें नईलाइन, लेकिन स्पेस और टैब (और एनयूएल इन zsh
) भी शामिल है और प्रत्येक शब्द पर ग्लोबिंग करता है (सिवाय में zsh
) (और यहां तक कि ksh93 या pdksh डेरिवेटिव में ब्रेस विस्तार!)।
भले ही आप इसे बनाते हैं:
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
यह अभी भी गलत है क्योंकि न्यूलाइन वर्ण फ़ाइल पथ में किसी भी मान्य है। इसका आउटपुट find -print
केवल पोस्ट-प्रोसेस करने योग्य मज़बूती से नहीं है (कुछ जटिल चाल का उपयोग छोड़कर, जैसा कि यहां दिखाया गया है )।
इसका मतलब यह भी है कि शेल को find
पूरी तरह से आउटपुट को स्टोर करने की आवश्यकता है , और फिर फ़ाइलों पर लूप शुरू करने से पहले इसे (जो कि आउटपुट को दूसरी बार मेमोरी में स्टोर करने का मतलब है) ग्लोब को विभाजित करें।
ध्यान दें कि find . | xargs cmd
ऐसी ही समस्याएं हैं (वहाँ, ब्लैंक, न्यूलाइन, सिंगल कोट, डबल कोट और बैकस्लैश (और कुछ xarg
कार्यान्वयन के साथ बाइट्स वैध वर्णों का हिस्सा नहीं हैं) एक समस्या है)
अधिक सही विकल्प
समर्थन और उपयोग करने के लिए for
आउटपुट पर एक लूप का उपयोग करने का एकमात्र तरीका find
होगा :zsh
IFS=$'\0'
IFS=$'\0'
for f in $(find . -print0)
(की जगह -print0
के साथ -exec printf '%s\0' {} +
के लिए find
कार्यान्वयन कि अमानक (लेकिन बहुत आम आजकल) का समर्थन नहीं करते -print0
)।
यहाँ, सही और पोर्टेबल तरीका उपयोग करना है -exec
:
find . -exec something with {} \;
या यदि something
एक से अधिक तर्क ले सकते हैं:
find . -exec something with {} +
यदि आपको शेल द्वारा नियंत्रित की जाने वाली फ़ाइलों की सूची की आवश्यकता है:
find . -exec sh -c '
for file do
something < "$file"
done' find-sh {} +
(खबरदार यह एक से अधिक शुरू हो सकता है sh
)।
कुछ प्रणालियों पर, आप उपयोग कर सकते हैं:
find . -print0 | xargs -r0 something with
कि मानक वाक्य रचना पर थोड़ा लाभ दिया है और इसका मतलब है, हालांकि something
की stdin
या तो पाइप या है /dev/null
।
एक कारण आप का उपयोग करना चाहते हो सकता है कि समानांतर प्रसंस्करण के लिए -P
GNU के विकल्प का उपयोग किया जा सकता है xargs
। stdin
मुद्दा भी जीएनयू के साथ चारों ओर काम किया जा सकता है xargs
के साथ -a
प्रक्रिया प्रतिस्थापन समर्थन के गोले के साथ विकल्प:
xargs -r0n 20 -P 4 -a <(find . -print0) something
उदाहरण के लिए, something
20 फ़ाइल तर्क लेने वाले प्रत्येक के 4 समवर्ती चालान तक चलाने के लिए ।
साथ zsh
या bash
, के उत्पादन से अधिक पाश करने के लिए एक और तरीका है find -print0
के साथ है:
while IFS= read -rd '' file <&3; do
something "$file" 3<&-
done 3< <(find . -print0)
read -d ''
न्यूलाइन सीमांकित के बजाय एनयूएल सीमांकित रिकॉर्ड पढ़ता है।
bash-4.4
और ऊपर के find -print0
साथ एक सरणी में लौटी फ़ाइलों को भी स्टोर कर सकते हैं :
readarray -td '' files < <(find . -print0)
zsh
बराबर (जो संरक्षण का लाभ दिया है find
के बाहर निकलने की स्थिति):
files=(${(0)"$(find . -print0)"})
साथ zsh
, आप find
ग्लोब क्वालिफायर के साथ पुनरावर्ती ग्लोबबिंग के संयोजन में अधिकांश अभिव्यक्तियों का अनुवाद कर सकते हैं । उदाहरण के लिए, लूपिंग ओवर find . -name '*.txt' -type f -mtime -1
:
for file (./**/*.txt(ND.m-1)) cmd $file
या
for file (**/*.txt(ND.m-1)) cmd -- $file
(की जरूरत के --
साथ सावधान रहना **/*
, फ़ाइल पथ के साथ शुरू नहीं कर रहे हैं ./
, इसलिए -
उदाहरण के लिए शुरू हो सकता है )।
ksh93
और bash
अंत **/
में पुनरावर्ती ग्लोबबिंग के अधिक अग्रिम रूपों के लिए समर्थन नहीं जोड़ा गया है , लेकिन अभी भी ग्लोब क्वालिफायर नहीं है जो **
वहां बहुत सीमित उपयोग करता है । यह भी सावधान रहें कि bash
निर्देशिका वृक्ष के नीचे उतरते समय 4.3 से पहले सहजीवन का पालन करें।
लूपिंग ओवर की तरह $(find .)
, इसका मतलब है कि मेमोरी 1 में फ़ाइलों की पूरी सूची को संग्रहीत करना । यह वांछनीय हो सकता है, हालांकि कुछ मामलों में जब आप फाइलों पर अपने कार्यों को फाइलों की खोज पर प्रभाव नहीं डालना चाहते हैं (जैसे जब आप अधिक फाइलें जोड़ते हैं जो अंत में खुद को पाया जा सकता है)।
अन्य विश्वसनीयता / सुरक्षा विचार
दौर कि शर्ते
अब, अगर हम विश्वसनीयता की बात कर रहे हैं, तो हमें समय के बीच की दौड़ की शर्तों का उल्लेख करना होगा find
/ zsh
एक फ़ाइल ढूंढनी होगी और जांच करनी होगी कि यह मानदंडों को पूरा करती है और समय का उपयोग किया जा रहा है ( TOCTOU race )।
यहां तक कि एक निर्देशिका पेड़ के नीचे उतरते समय, किसी को सहानुभूति का पालन न करने और TOCTOU दौड़ के बिना ऐसा करने के लिए सुनिश्चित करना होगा। find
(GNU find
कम से कम) यह करता है कि openat()
सही O_NOFOLLOW
झंडे (जहाँ समर्थित हो) का उपयोग करके निर्देशिकाओं को खोलकर और प्रत्येक निर्देशिका के लिए एक फाइल डिस्क्रिप्टर को खुला रखें, zsh
/ bash
/ ksh
ऐसा न करें। इसलिए एक हमलावर के सामने एक निर्देशिका को सही समय पर सिम्लिंक के साथ बदलने में सक्षम होने के कारण, आप गलत निर्देशिका को अवरूद्ध कर सकते हैं।
भले ही find
निर्देशिका को ठीक से, साथ -exec cmd {} \;
और इससे भी अधिक -exec cmd {} +
, एक बार cmd
निष्पादित किया गया हो, उदाहरण के लिए, cmd ./foo/bar
या cmd ./foo/bar ./foo/bar/baz
, जैसे ही समय का cmd
उपयोग करता है, नीचे उतरता है ./foo/bar
, bar
हो सकता है कि विशेषताएँ अब मापदंड से मेल नहीं खातीं find
, लेकिन इससे भी बदतर ./foo
हो सकती हैं। कुछ अन्य जगह पर एक सिमलिंक द्वारा प्रतिस्थापित (और दौड़ खिड़की बहुत से बड़ा किया जाता है -exec {} +
, जहां find
फोन करने के लिए पर्याप्त फ़ाइलों के लिए इंतजार कर रहा है cmd
)।
कुछ find
कार्यान्वयन में एक (गैर-मानक अभी तक) -execdir
दूसरी समस्या को कम करने के लिए विधेय है।
साथ में:
find . -execdir cmd -- {} \;
find
chdir()
चलाने से पहले फ़ाइल के मूल निर्देशिका में है cmd
। cmd -- ./foo/bar
कॉल करने के बजाय , यह कॉल करता है cmd -- ./bar
( cmd -- bar
कुछ कार्यान्वयनों के साथ, इसलिए --
), इसलिए ./foo
सिम्लिंक में परिवर्तित होने की समस्या से बचा जाता है। यह rm
सुरक्षित जैसे आदेशों का उपयोग करता है (यह अभी भी एक अलग फ़ाइल को हटा सकता है, लेकिन एक अलग निर्देशिका में एक फ़ाइल नहीं है), लेकिन उन आदेशों को नहीं जो फाइलों को संशोधित कर सकते हैं जब तक कि उन्हें सिमिलिंक का पालन न करने के लिए डिज़ाइन नहीं किया गया हो।
-execdir cmd -- {} +
कभी-कभी यह भी काम करता है लेकिन GNU के कुछ संस्करणों सहित कई कार्यान्वयन के साथ find
, यह इसके बराबर है -execdir cmd -- {} \;
।
-execdir
बहुत गहरी निर्देशिका पेड़ों से जुड़ी कुछ समस्याओं के आसपास काम करने का लाभ भी है।
में:
find . -exec cmd {} \;
दिए गए पथ का आकार cmd
, फ़ाइल की निर्देशिका की गहराई के साथ बढ़ेगा। यदि वह आकार PATH_MAX
(लिनक्स पर 4k की तरह कुछ) से बड़ा हो जाता है , तो cmd
उस पथ पर जो भी सिस्टम कॉल करता है वह एक ENAMETOOLONG
त्रुटि के साथ विफल हो जाएगा ।
के साथ -execdir
, केवल फ़ाइल नाम (संभवतः के साथ उपसर्ग किया जाता है ./
) को पास किया जाता है cmd
। अधिकांश फ़ाइल सिस्टम पर फ़ाइल नाम की NAME_MAX
तुलना में बहुत कम सीमा ( ) है PATH_MAX
, इसलिए ENAMETOOLONG
त्रुटि का सामना करने की संभावना कम है।
बाइट्स बनाम वर्ण
इसके अलावा, अक्सर सुरक्षा के बारे में विचार करते समय अनदेखी की जाती है find
और आम तौर पर सामान्य रूप से फ़ाइल नामों को संभालने के साथ और अधिक तथ्य यह है कि अधिकांश यूनिक्स जैसी प्रणालियों पर, फ़ाइल नाम बाइट्स के अनुक्रम (किसी भी बाइट मान लेकिन एक फ़ाइल पथ में 0, और अधिकांश सिस्टम पर हैं) ASCII आधारित वाले, हम अब के लिए दुर्लभ EBCDIC आधारित लोगों की उपेक्षा करेंगे) 0x2f पथ सीमांकक है)।
यह तय करना अनुप्रयोगों पर निर्भर है कि क्या वे उन बाइट्स को पाठ के रूप में मानना चाहते हैं। और वे आम तौर पर करते हैं, लेकिन आम तौर पर बाइट्स से पात्रों तक का अनुवाद उपयोगकर्ता के स्थान पर, पर्यावरण के आधार पर किया जाता है।
इसका मतलब यह है कि किसी दिए गए फ़ाइल नाम में स्थान के आधार पर भिन्न पाठ प्रतिनिधित्व हो सकता है। उदाहरण के लिए, बाइट अनुक्रम एक फ़ाइल के नाम के लिए उस स्थान में एक फ़ाइल नाम की व्याख्या करने के लिए 63 f4 74 e9 2e 74 78 74
होगा côté.txt
जहां चरित्र सेट ISO-8859-1 है, और cєtщ.txt
एक लोकल में जहां charset IS0-8859-5 है।
और भी बुरा। एक लोकल में, जहाँ charset UTF-8 (आजकल का मानक) है, 63 f4 74 e9 2e 74 78 74 बस अक्षरों से मैप नहीं किया जा सकता है!
find
ऐसा एक अनुप्रयोग है जो फ़ाइल नामों को अपने -name
/ -path
विधेय के लिए पाठ के रूप में मानता है (और अधिक, जैसे -iname
या -regex
कुछ कार्यान्वयन के साथ)।
इसका मतलब यह है कि उदाहरण के लिए, कई find
कार्यान्वयन के साथ (GNU सहित find
)।
find . -name '*.txt'
63 f4 74 e9 2e 74 78 74
जब हमारी UTF-8 लोकेल में कॉल की गई *
(जो 0 या अधिक वर्णों से मेल खाती है , बाइट्स से नहीं) तो उन गैर-वर्णों से मेल नहीं खा सकता है जब हमारी फ़ाइल ऊपर नहीं मिलेगी ।
LC_ALL=C find...
समस्या के चारों ओर काम करेगा क्योंकि सी लोकेल प्रति चरित्र एक बाइट का अर्थ है और (आमतौर पर) गारंटी देता है कि सभी बाइट मान एक चरित्र के लिए मैप करते हैं (कुछ बाइट मानों के लिए संभवतः अपरिभाषित)।
अब जब शेल से उन फ़ाइल नामों पर लूपिंग की बात आती है, तो वह बाइट बनाम चरित्र भी एक समस्या बन सकती है। हम आम तौर पर उस संबंध में 4 मुख्य प्रकार के गोले देखते हैं:
जो अभी भी बहु-बाइट की तरह जागरूक नहीं हैं dash
। उनके लिए, एक चरित्र को बाइट मैप करता है। उदाहरण के लिए, UTF-8 में, côté
4 वर्ण हैं, लेकिन 6 बाइट्स हैं। एक लोकल में जहां UTF-8 चारसेट है, में
find . -name '????' -exec dash -c '
name=${1##*/}; echo "${#name}"' sh {} \;
find
उन फ़ाइलों को सफलतापूर्वक ढूँढेगा जिनके नाम में UTF-8 में एन्कोडेड 4 वर्ण हैं, लेकिन dash
4 और 24 के बीच लंबाई की रिपोर्ट करेंगे।
yash
: विलोम। यह केवल पात्रों से संबंधित है । सभी इनपुट को आंतरिक रूप से वर्णों में अनुवादित किया जाता है। यह सबसे सुसंगत शेल के लिए बनाता है, लेकिन इसका मतलब यह भी है कि यह मनमाने ढंग से बाइट अनुक्रमों के साथ सामना नहीं कर सकता है (जो मान्य वर्णों में अनुवाद नहीं करते हैं)। यहां तक कि सी लोकेल में, यह 0x7f से ऊपर के बाइट मूल्यों के साथ सामना नहीं कर सकता है।
find . -exec yash -c 'echo "$1"' sh {} \;
côté.txt
उदाहरण के लिए, पहले से हमारे ISO-8859-1 पर UTF-8 लोकेल विफल हो जाएगा ।
बहु-बाइट समर्थन को पसंद किया गया है bash
या zsh
जहां उन लोगों को उत्तरोत्तर जोड़ा गया है। वे उन बाइट्स पर विचार करने से पीछे हट जाएंगे जिन्हें पात्रों के लिए मैप नहीं किया जा सकता है जैसे कि वे वर्ण थे। उनके पास अभी भी यहाँ कुछ कीड़े हैं और विशेष रूप से कम आम मल्टी-बाइट चारसेट जैसे GBK या BIG5-HKSCS (जिनके काफी मल्टी-बाइट कैरेक्टर होने के कारण 0-127 रेंज में बाइट्स होते हैं (जैसे ASCII अक्षर) )।
जैसे उन sh
FreeBSD के (11 कम से कम) या mksh -o utf8-mode
कि समर्थन बहु बाइट लेकिन केवल UTF-8 के लिए।
टिप्पणियाँ
1 पूर्णता के लिए, हम zsh
पूरी सूची में पूरी सूची को संग्रहीत किए बिना पुनरावर्ती ग्लोबिंग का उपयोग करके फ़ाइलों पर लूप का एक हैक करने का तरीका बता सकते हैं :
process() {
something with $REPLY
false
}
: **/*(ND.m-1+process)
+cmd
एक ग्लोब क्वालीफ़ायर है जो cmd
वर्तमान फ़ाइल पथ के साथ कॉल करता है (आमतौर पर एक फ़ंक्शन) $REPLY
। यह तय करने के लिए फ़ंक्शन सही या गलत है कि क्या फ़ाइल का चयन किया जाना चाहिए (और $REPLY
एक $reply
सरणी में कई फ़ाइलों को संशोधित या वापस भी कर सकता है )। यहां हम उस फंक्शन में प्रोसेसिंग करते हैं और गलत रिटर्न करते हैं ताकि फाइल का चयन न हो।