लूपिंग का आउटपुट खराब अभ्यास पर क्यों है?


170

यह प्रश्न किससे प्रेरित है

पाठ को संसाधित करने के लिए शेल लूप का उपयोग करना बुरा व्यवहार क्यों माना जाता है?

मैं इन निर्माणों को देखता हूं

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

तथा

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

यहाँ लगभग दैनिक आधार पर उपयोग किया जा रहा है, भले ही कुछ लोग उन पदों पर टिप्पणी करने के लिए समय लेते हैं, यह बताते हुए कि इस तरह के सामान से क्यों बचा जाना चाहिए ...
ऐसी पोस्टों की संख्या (और कभी-कभी उन टिप्पणियों को अनदेखा कर दिया जाता है) मैंने सोचा कि मैं भी एक सवाल पूछ सकता हूं:

क्यों लूपिंग की findखराब प्रैक्टिस खत्म हो गई है और प्रत्येक फाइल के नाम / रास्ते के लिए एक या एक से अधिक कमांड चलाने का उचित तरीका क्या है find?


12
मुझे लगता है कि यह "नेवर पार्स एलएस आउटपुट!" - आप निश्चित रूप से एक या एक बंद आधार पर कर सकते हैं, लेकिन वे उत्पादन की गुणवत्ता की तुलना में एक त्वरित हैक के अधिक हैं। या, आम तौर पर, निश्चित रूप से कभी हठधर्मिता नहीं होनी चाहिए।
ब्रूस एडगर


इसे एक विहित उत्तर में बदल दिया जाना चाहिए
ज़ेड

6
क्योंकि खोजने की बात यह है कि यह जो खोजता है, उस पर लूप होता है।
ऑरेंजडॉग

2
एक सहायक बिंदु - आप फ़ाइल में आउटपुट भेजना चाहते हैं, और फिर बाद में इसे स्क्रिप्ट में संसाधित कर सकते हैं। यदि आपको स्क्रिप्ट को डीबग करने की आवश्यकता होती है तो फ़ाइल सूची समीक्षा के लिए उपलब्ध है।
user117529

जवाबों:


87

समस्या

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होगा :zshIFS=$'\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

एक कारण आप का उपयोग करना चाहते हो सकता है कि समानांतर प्रसंस्करण के लिए -PGNU के विकल्प का उपयोग किया जा सकता है xargsstdinमुद्दा भी जीएनयू के साथ चारों ओर काम किया जा सकता है xargsके साथ -aप्रक्रिया प्रतिस्थापन समर्थन के गोले के साथ विकल्प:

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

उदाहरण के लिए, something20 फ़ाइल तर्क लेने वाले प्रत्येक के 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()चलाने से पहले फ़ाइल के मूल निर्देशिका में है cmdcmd -- ./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 मुख्य प्रकार के गोले देखते हैं:

  1. जो अभी भी बहु-बाइट की तरह जागरूक नहीं हैं dash। उनके लिए, एक चरित्र को बाइट मैप करता है। उदाहरण के लिए, UTF-8 में, côté4 वर्ण हैं, लेकिन 6 बाइट्स हैं। एक लोकल में जहां UTF-8 चारसेट है, में

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

    findउन फ़ाइलों को सफलतापूर्वक ढूँढेगा जिनके नाम में UTF-8 में एन्कोडेड 4 वर्ण हैं, लेकिन dash4 और 24 के बीच लंबाई की रिपोर्ट करेंगे।

  2. yash: विलोम। यह केवल पात्रों से संबंधित है । सभी इनपुट को आंतरिक रूप से वर्णों में अनुवादित किया जाता है। यह सबसे सुसंगत शेल के लिए बनाता है, लेकिन इसका मतलब यह भी है कि यह मनमाने ढंग से बाइट अनुक्रमों के साथ सामना नहीं कर सकता है (जो मान्य वर्णों में अनुवाद नहीं करते हैं)। यहां तक ​​कि सी लोकेल में, यह 0x7f से ऊपर के बाइट मूल्यों के साथ सामना नहीं कर सकता है।

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

    côté.txtउदाहरण के लिए, पहले से हमारे ISO-8859-1 पर UTF-8 लोकेल विफल हो जाएगा ।

  3. बहु-बाइट समर्थन को पसंद किया गया है bashया zshजहां उन लोगों को उत्तरोत्तर जोड़ा गया है। वे उन बाइट्स पर विचार करने से पीछे हट जाएंगे जिन्हें पात्रों के लिए मैप नहीं किया जा सकता है जैसे कि वे वर्ण थे। उनके पास अभी भी यहाँ कुछ कीड़े हैं और विशेष रूप से कम आम मल्टी-बाइट चारसेट जैसे GBK या BIG5-HKSCS (जिनके काफी मल्टी-बाइट कैरेक्टर होने के कारण 0-127 रेंज में बाइट्स होते हैं (जैसे ASCII अक्षर) )।

  4. जैसे उन shFreeBSD के (11 कम से कम) या mksh -o utf8-modeकि समर्थन बहु बाइट लेकिन केवल UTF-8 के लिए।

टिप्पणियाँ

1 पूर्णता के लिए, हम zshपूरी सूची में पूरी सूची को संग्रहीत किए बिना पुनरावर्ती ग्लोबिंग का उपयोग करके फ़ाइलों पर लूप का एक हैक करने का तरीका बता सकते हैं :

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

+cmdएक ग्लोब क्वालीफ़ायर है जो cmdवर्तमान फ़ाइल पथ के साथ कॉल करता है (आमतौर पर एक फ़ंक्शन) $REPLY। यह तय करने के लिए फ़ंक्शन सही या गलत है कि क्या फ़ाइल का चयन किया जाना चाहिए (और $REPLYएक $replyसरणी में कई फ़ाइलों को संशोधित या वापस भी कर सकता है )। यहां हम उस फंक्शन में प्रोसेसिंग करते हैं और गलत रिटर्न करते हैं ताकि फाइल का चयन न हो।


यदि zsh और bash उपलब्ध हैं, तो आप सुरक्षित रूप से व्यवहार करने के लिए प्रयास करने के बजाय ग्लॉबिंग और शेल कंस्ट्रक्शंस का उपयोग करने से बेहतर हो सकतेfind हैं। डिफ़ॉल्ट रूप से असुरक्षित होने पर ग्लोबिंग डिफ़ॉल्ट रूप से सुरक्षित है।
केविन

@ केविन, संपादित देखें।
स्टीफन चेज़लस

182

क्यों लूपिंग की findखराब प्रैक्टिस खत्म हो गई है?

सरल उत्तर है:

क्योंकि फ़ाइल नाम में कोई भी वर्ण हो सकता है ।

इसलिए, कोई प्रिंट करने योग्य चरित्र नहीं है जिसे आप मज़बूती से फ़ाइलनाम के उपयोग में ला सकते हैं।


नई लाइनों का उपयोग अक्सर (गलत तरीके से) फाइलनामों को परिसीमित करने के लिए किया जाता है, क्योंकि फ़ाइल नाम में नए वर्णों को शामिल करना असामान्य है।

हालाँकि, यदि आप अपने सॉफ़्टवेयर का निर्माण मनमाने ढंग से मान्यताओं के आसपास करते हैं, तो आप कम से कम असामान्य मामलों को संभालने में विफल रहते हैं, और अपने सिस्टम के नियंत्रण को दूर करने वाले दुर्भावनापूर्ण कारनामों के लिए खुद को सबसे कम खोलते हैं। तो यह मजबूती और सुरक्षा का सवाल है।

यदि आप दो अलग-अलग तरीकों से सॉफ़्टवेयर लिख सकते हैं, और उनमें से एक किनारे के मामलों (असामान्य इनपुट) को सही ढंग से संभालता है, लेकिन दूसरे को पढ़ना आसान है, तो आप तर्क दे सकते हैं कि एक व्यापार है। (मैं नहीं करूंगा। मैं सही कोड पसंद करूंगा।)

हालांकि, यदि कोड का सही, मजबूत संस्करण भी पढ़ना आसान है, तो कोड लिखने का कोई बहाना नहीं है जो कि किनारे के मामलों में विफल रहता है। ऐसा findपाया गया है और प्रत्येक फ़ाइल पर एक कमांड चलाने की आवश्यकता है।


आइए अधिक विशिष्ट हों: एक यूनिक्स या लिनक्स सिस्टम पर, फ़ाइलनाम में कोई भी चरित्र हो सकता है सिवाय इसके /(जो पथ घटक विभाजक के रूप में उपयोग किया जाता है), और उनमें एक अशक्त बाइट नहीं हो सकता है।

एक अशक्त बाइट इसलिए केवल फ़ाइल नाम परिसीमन करने का सही तरीका है।


चूंकि GNU findमें एक -print0प्राथमिक शामिल होता है, जो इसे प्रिंट करने वाले फ़ाइलनामों के परिसीमन के लिए एक अशक्त बाइट का उपयोग करेगा, GNU find को सुरक्षित रूप से GNU xargsऔर इसके -0ध्वज (और -rध्वज) के साथ प्रयोग किया जा सकता है find:

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

हालाँकि, इस रूप का उपयोग करने का कोई अच्छा कारण नहीं है , क्योंकि:

  1. यह जीएनयू खोज पर एक निर्भरता जोड़ता है जिसे वहां होने की आवश्यकता नहीं है, और
  2. findइसे खोजने वाली फ़ाइलों पर कमांड चलाने में सक्षम होने के लिए डिज़ाइन किया गया है।

इसके अलावा, GNU की xargsआवश्यकता है -0और -r, जबकि FreeBSD के लिए xargsकेवल -0(और कोई -rविकल्प नहीं है) की आवश्यकता है, और कुछ xargsबिल्कुल भी समर्थन नहीं करते -0हैं। तो यह सिर्फ find(अगले अनुभाग देखें) और छोड़ें की POSIX सुविधाओं से चिपके रहने के लिए सबसे अच्छा है xargs

बिंदु 2 के लिए - findयह खोजने वाली फ़ाइलों पर कमांड चलाने की क्षमता है - मुझे लगता है कि माइक लौकाइड्स ने इसे सबसे अच्छा कहा:

findव्यवसाय अभिव्यक्ति का मूल्यांकन कर रहा है - फाइलों का पता लगाना नहीं। हाँ, findनिश्चित रूप से फ़ाइलों का पता लगाता है; लेकिन यह वास्तव में सिर्फ एक साइड इफेक्ट है।

- यूनिक्स पावर टूल्स


POSIX के निर्दिष्ट उपयोग हैं find

प्रत्येक findपरिणाम के लिए एक या एक से अधिक कमांड चलाने का उचित तरीका क्या है ?

प्रत्येक फ़ाइल के लिए एकल कमांड चलाने के लिए, उपयोग करें:

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

प्रत्येक फ़ाइल के लिए अनुक्रम में कई कमांड चलाने के लिए, जहां दूसरी कमांड केवल तभी चलनी चाहिए जब पहला कमांड सफल हो, उपयोग करें:

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

एक साथ कई फाइलों पर एक ही कमांड चलाने के लिए:

find dirname ... -exec somecommand {} +

find के साथ सम्मिलन में sh

यदि आपको कमांड में शेल फीचर्स का उपयोग करने की आवश्यकता है , जैसे आउटपुट को पुनर्निर्देशित करना या फाइलनेम से एक्सटेंशन को अलग करना या कुछ इसी तरह, आप sh -cकंस्ट्रक्शन का उपयोग कर सकते हैं । आपको इसके बारे में कुछ बातें पता होनी चाहिए:

  • कोड {}में सीधे एम्बेड करें sh। यह दुर्भावनापूर्ण रूप से तैयार किए गए फ़ाइल नाम से मनमाना कोड निष्पादन की अनुमति देता है। इसके अलावा, यह वास्तव में POSIX द्वारा निर्दिष्ट भी नहीं है कि यह बिल्कुल काम करेगा। (अगला बिंदु देखें।)

  • {}कई बार उपयोग न करें , या इसे लंबे तर्क के भाग के रूप में उपयोग करें। यह पोर्टेबल नहीं है। उदाहरण के लिए, ऐसा न करें:

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

    POSIX विनिर्देशोंfind को उद्धृत करने के लिए :

    एक तो utility_name या तर्क स्ट्रिंग दो अक्षर "{}", लेकिन नहीं सिर्फ दो वर्ण हैं "{}", यह कार्यान्वयन से परिभाषित है कि क्या है खोजने के उन दो पात्रों की जगह या बदलाव के बिना स्ट्रिंग का उपयोग करता है।

    ... यदि दो वर्ण "{}" वाले एक से अधिक तर्क मौजूद हैं, तो व्यवहार अनिर्दिष्ट है।

  • -cविकल्प से पास किए गए शेल कमांड स्ट्रिंग के बाद के तर्क शेल के स्थितीय मापदंडों पर सेट होते हैं, जिनके साथ शुरू होता है$0 । से शुरू नहीं हो रहा है $1

    इस कारण से, "डमी" $0मान को शामिल करना अच्छा है , जैसे find-shकि स्पॉल किए गए शेल के भीतर से त्रुटि रिपोर्टिंग के लिए उपयोग किया जाएगा। इसके अलावा, यह "$@"शेल के लिए कई फ़ाइलों को पास करते समय निर्माण का उपयोग करने की अनुमति देता है , जबकि मान के लिए छोड़ने का $0मतलब यह होगा कि पास की गई पहली फ़ाइल सेट हो जाएगी $0और इस तरह इसमें शामिल नहीं होगी "$@"


प्रति फ़ाइल एक एकल शेल कमांड चलाने के लिए, उपयोग करें:

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

हालाँकि, यह आमतौर पर शेल लूप में फ़ाइलों को संभालने के लिए बेहतर प्रदर्शन देगा, ताकि आपको मिली हर एक फ़ाइल के लिए एक शेल न हो:

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

(ध्यान दें कि बदले में प्रत्येक स्थितीय मापदंडों के for f doबराबर है for f in "$@"; doऔर संभालता है - दूसरे शब्दों में, यह findउनके नाम पर किसी विशेष वर्ण की परवाह किए बिना, मिली हुई प्रत्येक फ़ाइलों का उपयोग करता है ।)


सही findउपयोग के आगे के उदाहरण :

(नोट: इस सूची का विस्तार करने के लिए स्वतंत्र महसूस करें।)


5
एक ऐसा मामला है जहां मुझे पार्सिंग findके आउटपुट के विकल्प का पता नहीं है - जहां आपको प्रत्येक फ़ाइल के लिए वर्तमान शेल (जैसे कि आप चर सेट करना चाहते हैं) में कमांड चलाने की आवश्यकता है । इस मामले में, while IFS= read -r -u3 -d '' file; do ... done 3< <(find ... -print0)मुझे पता है सबसे अच्छा मुहावरा है। नोट्स: <( )पोर्टेबल नहीं है - बैश या zsh का उपयोग करें। इसके अलावा, -u3और 3<वहाँ हैं अगर लूप के अंदर कुछ भी स्टडिन पढ़ने की कोशिश करता है।
गॉर्डन डेविसन

1
@GordonDavisson, शायद - लेकिन आपको उन चर के लिए क्या सेट करने की आवश्यकता है ? मैं है जो कुछ भी यह संभाला जाना चाहिए कि लोगों का तर्क था अंदरfind ... -exec कॉल। या बस एक शेल ग्लोब का उपयोग करें, अगर यह आपके उपयोग के मामले को संभाल लेगा।
वाइल्डकार्ड

1
मैं अक्सर फ़ाइलों को संसाधित करने के बाद एक सारांश प्रिंट करना चाहता हूं ("2 परिवर्तित, 3 छोड़ दिया गया, निम्न फ़ाइलों में त्रुटियां थीं: ..."), और उन गणनाओं / सूचियों को शेल चरों में जमा करना होगा। इसके अलावा, ऐसी परिस्थितियाँ हैं, जहाँ मैं फ़ाइल नाम का एक सरणी बनाना चाहता हूँ ताकि मैं क्रम में iterate की तुलना में अधिक जटिल चीजें कर सकूँ (उस स्थिति में filelist=(); while ... do filelist+=("$file"); done ...)।
गॉर्डन डेविसन 23

3
आपका उत्तर सही है। हालाँकि मुझे हठधर्मिता पसंद नहीं है। भले ही मैं बेहतर जानता हूं, ऐसे कई (विशेष रूप से इंटरैक्टिव) मामलों का उपयोग करते हैं जहां यह सुरक्षित है और findआउटपुट पर लूपिंग टाइप करना आसान है या खराब उपयोग भी ls। मैं समस्याओं के बिना यह दैनिक कर रहा हूं। मैं -0 के बारे में जानता हूं, --null, -z या -0 सभी प्रकार के टूल के विकल्प। लेकिन मैं अपने इंटरैक्टिव शेल प्रॉम्प्ट पर उनका उपयोग करने के लिए समय बर्बाद नहीं करूंगा जब तक कि वास्तव में जरूरत न हो। यह आपके उत्तर में भी नोट किया जा सकता है।
रदिमीयेर r

16
@rudimeier, हठधर्मिता बनाम सबसे अच्छा अभ्यास पर तर्क पहले से ही मौत के लिए किया गया है । रुचि नहीं। यदि आप इसे अंतःक्रियात्मक रूप से उपयोग करते हैं और यह काम करता है, तो ठीक है, आपके लिए अच्छा है- लेकिन मैं ऐसा करने का प्रचार नहीं करने जा रहा हूं। स्क्रिप्ट लेखकों का प्रतिशत जो यह जानने के लिए परेशान है कि कोड कितना मजबूत है और फिर केवल प्रोडक्शन स्क्रिप्ट लिखते समय, यह करने के बजाय कि वे इंटरैक्टिव तरीके से जो कुछ भी करने के लिए उपयोग किए जाते हैं , वह बेहद कम है। हैंडलिंग हर समय सर्वोत्तम प्रथाओं को बढ़ावा देना है। लोगों को यह सीखने की जरूरत है कि चीजों को करने का एक सही तरीका है।
वाइल्डकार्ड 20

10

यह जवाब मुख्य रूप से बहुत बड़े परिणाम सेट और चिंताओं के प्रदर्शन के लिए है, उदाहरण के लिए जब धीमी नेटवर्क पर फ़ाइलों की सूची प्राप्त करना। छोटी मात्रा में फ़ाइलों के लिए (कुछ 100 या शायद एक स्थानीय डिस्क पर भी 1000 कहें) इसमें से अधिकांश मूट है।

समानांतरवाद और स्मृति उपयोग

दिए गए अन्य उत्तरों के अलावा, जुदाई की समस्याओं और इस तरह से संबंधित है, के साथ एक और मुद्दा है

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

लाइनब्रेक पर विभाजित होने से पहले बैकटिक्स के अंदर के हिस्से का पूरी तरह से मूल्यांकन किया जाना चाहिए। इसका मतलब है, अगर आपको भारी मात्रा में फाइलें मिलती हैं, तो यह या तो विभिन्न घटकों में जो भी आकार सीमाएं हैं, उन पर चोक हो सकता है; अगर कोई सीमा नहीं है तो आप स्मृति से बाहर भाग सकते हैं; और किसी भी स्थिति में आपको तब तक इंतजार करना होगा जब तक कि पूरी सूची का उत्पादन नहीं हो जाता है findऔर तब forतक अपना पहला रन देने से पहले ही पार्स कर दिया जाता है smth

पसंदीदा यूनिक्स तरीका पाइपों के साथ काम करना है, जो स्वाभाविक रूप से समानांतर में चल रहे हैं, और जिन्हें सामान्य रूप से मनमाने ढंग से विशाल बफ़र्स की भी आवश्यकता नहीं है। इसका मतलब है कि: आप findअपने समानांतर चलने के लिए बहुत कुछ पसंद करेंगे smth, और केवल वर्तमान फ़ाइल का नाम रैम में रखें, जबकि यह उसके हाथ में है smth

उस के लिए कम से कम आंशिक रूप से ठीक समाधान उपर्युक्त है find -exec smth। यह सभी फ़ाइल नामों को स्मृति में रखने की आवश्यकता को हटा देता है और समानांतर में अच्छी तरह से चलता है। दुर्भाग्य से, यह smthप्रति फ़ाइल एक प्रक्रिया भी शुरू करता है। यदि smthकेवल एक फ़ाइल पर काम किया जा सकता है, तो यह उसी तरह से होना चाहिए।

यदि संभव हो तो, इष्टतम समाधान अपने STDIN पर फ़ाइल नामों को संसाधित करने में सक्षम होने के find -print0 | smthसाथ होगा smth। फिर आपके पास केवल एक smthप्रक्रिया है, चाहे कितनी भी फाइलें हों, और आपको दो प्रक्रियाओं के बीच केवल बाइट्स (जो भी आंतरिक पाइप बफ़रिंग चल रहा है) की एक छोटी मात्रा को बफर करने की आवश्यकता है। यदि smthयह एक मानक यूनिक्स / POSIX कमांड है, तो बेशक, यह अवास्तविक है, लेकिन यदि आप इसे स्वयं लिख रहे हैं तो यह एक दृष्टिकोण हो सकता है।

यदि यह संभव नहीं है, तो find -print0 | xargs -0 smth, संभावना है, बेहतर समाधानों में से एक है। जैसा कि @ dave_thompson_085 ने टिप्पणियों में उल्लेख किया है, जब सिस्टम की सीमाएँ (डिफ़ॉल्ट रूप से, 128 KB की सीमा में या सिस्टम द्वारा जो भी सीमा लागू की जाती है) के xargsकई रनों पर तर्क विभाजित करता है , और कितने को प्रभावित करने के विकल्प हैं फ़ाइलों को एक कॉल के लिए दिया जाता है , इसलिए प्रक्रियाओं की संख्या और प्रारंभिक देरी के बीच एक संतुलन खोजना है ।smthexecsmthsmth

संपादित करें: "सर्वश्रेष्ठ" की धारणाओं को हटा दिया - यह कहना मुश्किल है कि क्या कुछ बेहतर होगा। ;)


find ... -exec smth {} +समाधान है।
वाइल्डकार्ड

find -print0 | xargs smthबिल्कुल भी काम नहीं करता है, लेकिन find -print0 | xargs -0 smth(नोट -0) या find | xargs smthअगर फ़ाइलनामों में व्हाट्सएप उद्धरण या बैकस्लैश नहीं है, तो एक smthके रूप में कई फाइलनाम उपलब्ध हैं और एक तर्क सूची में फिट हैं ; यदि आप अधिकतम सीमा पार कर जाते हैं, तो यह smthदिए गए सभी आरों (कोई सीमा नहीं) को संभालने के लिए जितनी बार आवश्यकता होती है, उतनी बार चलता है । आप छोटे 'विखंडू' (इस प्रकार कुछ हद तक समानांतरवाद) के साथ सेट कर सकते हैं -L/--max-lines -n/--max-args -s/--max-chars
dave_thompson_085


4

एक कारण यह है कि व्हॉट्सएप ने कामों में एक स्पैनर फेंका, जिससे फाइल 'फू बार' का मूल्यांकन 'फू' और 'बार' के रूप में हो गया।

$ 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 इसके बजाय उपयोग किया जाता है

$ 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: -
$

विशेष रूप से इस मामले में findक्योंकि हर फ़ाइल पर एक कमांड निष्पादित करने का एक विकल्प है जो आसानी से सबसे अच्छा विकल्प है।
सेंटिमेन

1
भी विचार -exec ... {} \;बनाम-exec ... {} +
thrig

1
यदि आप उपयोग करते हैं for file in "$(find . -type f)" और echo "${file}"फिर यह व्हाट्सएप के साथ भी काम करता है, तो अन्य विशेष वर्ण मुझे अधिक परेशानी का कारण
लगते हैं

9
@mazs - नहीं, उद्धृत करना वह नहीं करता जो आप सोचते हैं। कई फ़ाइलों के साथ एक निर्देशिका में for file in "$(find . -type f)";do printf '%s %s\n' name: "${file}";done(जो आपके अनुसार) अलग-अलग लाइन पर प्रत्येक फ़ाइल का नाम प्रिंट करना चाहिए name:। यह नहीं है
डॉन_क्रिस्टी

2

क्योंकि किसी भी कमांड का आउटपुट एक ही स्ट्रिंग है, लेकिन आपके लूप को लूप ओवर करने के लिए स्ट्रिंग्स की एक सरणी की आवश्यकता होती है। कारण यह है कि "काम करता है" यह है कि गोले ने आपके लिए व्हाट्सएप पर स्ट्रिंग को विभाजित किया है।

दूसरे, जब तक आपको किसी विशेष सुविधा की आवश्यकता न हो, इस बात findसे अवगत रहें कि आपका खोल सबसे अधिक संभावना है कि पहले से ही एक पुनरावर्ती ग्लोब पैटर्न का विस्तार किया जा सकता है, और महत्वपूर्ण रूप से, यह एक उचित सरणी तक विस्तारित होगा।

बैश उदाहरण:

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

मछली में भी:

for i in **
    echo «$i»
end

यदि आपको इसकी विशेषताओं की आवश्यकता है find, तो केवल NUL (जैसे find -print0 | xargs -r0मुहावरे) पर विभाजित करना सुनिश्चित करें ।

मछली NUL सीमांकित उत्पादन को पुनरावृत्त कर सकती है। तो यह वास्तव में बुरा नहीं है :

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

अंतिम थोड़ा गोचर के रूप में, कई गोले में (निश्चित रूप से मछली नहीं), कमांड आउटपुट पर लूपिंग से लूप बॉडी एक सबमिशल हो जाएगी (जिसका अर्थ है कि आप किसी भी तरह से एक वैरिएबल सेट नहीं कर सकते हैं जो लूप समाप्त होने के बाद दिखाई देता है), जो है कभी नहीं तुम क्या चाहते हो।


@don_crissti ठीक है। यह आम तौर पर काम नहीं करता है । मैं यह कहकर व्यंग्यात्मक होने की कोशिश कर रहा था कि यह "काम" (उद्धरण के साथ) है।
user2394284

ध्यान दें कि zsh90 के दशक की शुरुआत में पुनरावर्ती ग्लोबिंग की उत्पत्ति हुई (हालांकि आपको **/*वहां की आवश्यकता होगी )। fishहालांकि बैश के समतुल्य फीचर के पहले के कार्यान्वयन, हालांकि डायरेक्टरी ट्री को उतरते समय सहानुभूति का अनुसरण करते हैं। कार्यान्वयन के बीच के अंतर के लिए देखें ls *, ls ** और ls *** का परिणाम
स्टीफन चेजलस

1

खोज के आउटपुट पर लूपिंग करना बुरा अभ्यास नहीं है - क्या बुरा अभ्यास है (इस और सभी स्थितियों में) मान रहा है कि आपका इनपुट जानने के बजाय एक विशेष प्रारूप है (परीक्षण और पुष्टि) यह एक विशेष प्रारूप है।

tldr / CBF: find | parallel stuff

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.