2009-07-12 13 views
70

find . -print0 का उपयोग में -print0 रिक्त स्थान, नई-पंक्तियों, उद्धरण चिह्न आदिखोजने के आउटपुट कैप्चरिंग। एक पार्टी सरणी

हालांकि युक्त फ़ाइल नामों की संभावना की वजह से पार्टी में फ़ाइलों की एक सूची प्राप्त करने का सुरक्षित तरीका प्रतीत हो रहा है, मैं आ रही हैं मुश्किल समय वास्तव में खोज के आउटपुट को बैश के भीतर या अन्य कमांड लाइन उपयोगिताओं के साथ उपयोगी बनाते हैं। ,

find . -print0 | perl -e '$/="\0"; @files=<>; print $#files;' 

यह उदाहरण मिली फ़ाइलों की संख्या प्रिंट फ़ाइल नामों में नई-पंक्तियों के खतरे से बचने के भ्रष्ट: एक ही रास्ता मैं उत्पादन का उपयोग करने के कामयाब रहे अशक्त करने के लिए इसे पर्ल के लिए पाइप, और बदलते पर्ल के भारतीय विदेश सेवा के द्वारा होता है गिनती, के रूप में घटित होता साथ:

find . | wc -l 

सबसे कमांड लाइन कार्यक्रमों अशक्त-सीमांकित इनपुट का समर्थन नहीं है, मैं समझ सबसे अच्छी बात है, एक पार्टी सरणी में find . -print0 के उत्पादन पर कब्जा करने की तरह मैंने किया है किया जाएगा उपरोक्त पर्ल स्निपेट में, और फिर कार्य के साथ जारी रखें, जो कुछ भी हो सकता है।

मैं यह कैसे कर सकता हूं?

यह काम नहीं करता:

find . -print0 | (IFS=$'\0' ; array=($(cat)) ; echo ${#array[@]}) 

एक बहुत अधिक सामान्य प्रश्न हो सकता है: मैं कैसे बैश में फ़ाइलों की सूची के साथ उपयोगी चीजें कर सकते हैं?

+0

आप उपयोगी चीजों को करने से क्या मतलब है? –

+4

ओह, आप जानते हैं, सामान्य चीजें सरणी उपयोगी होती हैं: उनके आकार का पता लगाना; उनकी सामग्री पर पुनरावृत्ति; उन्हें पीछे की ओर प्रिंटिंग; उन्हें छंटाई। उस तरह की चीस। इन चीजों को डेटा के साथ करने के लिए यूनिक्स में उपयोगिताएं हैं: wc, bash's for loops, tac और क्रमशः क्रमशः; लेकिन उन सभी सूचियों से निपटने के दौरान बेकार लगते हैं जिनमें रिक्त स्थान या न्यूलाइन हो सकती है। अर्थात। फ़ाइल नाम। शून्य मूल्यवान इनपुट-फील्ड-सेपरेटर्स के साथ डेटा पिपिंग समाधान लगता है, लेकिन बहुत कम उपयोगिताओं इसे संभाल सकता है। – Idris

+1

यहां बहुत सारे विनिर्देशों के साथ खोल में फ़ाइल नामों को सही तरीके से कैसे प्रबंधित किया जाए, इस पर एक निबंध है: [http://www.dwheeler.com/essays/filenames-in-shell.html ](http://www.dwheeler.com/ निबंध/फ़ाइल नाम-इन-shell.html) –

उत्तर

95

बेशर्मी Greg's BashFAQ से चोरी:

unset a i 
while IFS= read -r -d $'\0' file; do 
    a[i++]="$file"  # or however you want to process each file 
done < <(find /tmp -type f -print0) 

ध्यान दें कि पुनर्निर्देशन निर्माण यहां इस्तेमाल किया (cmd1 < <(cmd2)) के समान है, लेकिन अधिक सामान्य पाइपलाइन (cmd2 | cmd1) के रूप में से भिन्न - अगर आदेशों खोल builtins हैं (उदा while), पाइपलाइन संस्करण उन्हें सबहेल में निष्पादित करता है, और वे सेट किए जाने वाले किसी भी चर (उदा। सरणी a) बाहर निकलने पर खो जाते हैं। cmd1 < <(cmd2) केवल एक सबहेल में cmd2 चलाता है, इसलिए सरणी इसके निर्माण से पहले रहता है। चेतावनी: पुनर्निर्देशन का यह रूप केवल बैश में उपलब्ध है, श-इम्यूलेशन मोड में भी बाश नहीं है; आपको #!/bin/bash के साथ अपनी स्क्रिप्ट शुरू करनी होगी।

इसके अलावा, क्योंकि फ़ाइल प्रोसेसिंग चरण (इस मामले में, केवल a[i++]="$file", लेकिन आप लूप में सीधे कुछ फैनसीयर करना चाहते हैं) इसके इनपुट को रीडायरेक्ट किया गया है, यह किसी भी कमांड का उपयोग नहीं कर सकता जो स्टडीन से पढ़ सकता है। इस सीमा से बचने के लिए, मैं का उपयोग करते हैं:

unset a i 
while IFS= read -r -u3 -d $'\0' file; do 
    a[i++]="$file"  # or however you want to process each file 
done 3< <(find /tmp -type f -print0) 

... जो इकाई 3 के माध्यम से फ़ाइल सूची से गुजरता है, बल्कि stdin से।

+0

अहह लगभग वहां ... यह अभी तक का सबसे अच्छा जवाब है। हालांकि, मैंने अभी इसकी निर्देशिका में एक नई लाइन वाली फाइल वाली एक निर्देशिका पर कोशिश की है, और echo $ {a [1]} का उपयोग करके उस तत्व का निरीक्षण करने पर, नई लाइन एक स्थान (0x20) बन गई है। कोई विचार क्यों यह हो रहा है? – Idris

+0

आप किस संस्करण का बैश चल रहे हैं? मुझे पुराने संस्करणों के साथ परेशानी हुई है (दुर्भाग्य से मुझे ठीक से याद नहीं है) स्ट्रिंग में न्यूलाइन और डिलीट ('177') से निपटने से नहीं। आईआईआरसी, यहां तक ​​कि x = "$ y" हमेशा इन वर्णों के साथ सही काम नहीं करेगा। मैंने बस 2.05b.0 और 3.2.17 के साथ परीक्षण किया (सबसे पुराना और नवीनतम मेरे पास आसान है); दोनों ने न्यूलाइन ठीक से संभाला, लेकिन v2.05b.0 हटाए गए चरित्र को खा लिया। –

+0

मैंने इसे ओएसएक्स पर 3.2.17, लिनक्स पर 3.2.3 9 और नेटबीएसडी पर 3.2.48 पर कोशिश की है; सभी अंतरिक्ष में नई लाइन बारी। – Idris

1

मुझे लगता है कि और अधिक सुरुचिपूर्ण समाधान मौजूद है, लेकिन मैं में यह एक टॉस जाएगा यह भी रिक्त स्थान और/या नई-पंक्तियों के साथ फ़ाइल नामों के लिए काम करेंगे:।

i=0; 
for f in *; do 
    array[$i]="$f" 
    ((i++)) 
done 

फिर आप जैसे कर सकते हैं सूची फ़ाइलें एक के बाद एक (उलटे क्रम में इस मामले में):

for ((i = $i - 1; i >= 0; i--)); do 
    ls -al "${array[$i]}" 
done 

This page एक अच्छा उदाहरण देता है, और अधिक Advanced Bash-Scripting Guide में Chapter 26 देखने के लिए।

+0

यह (और नीचे दिए गए अन्य समान उदाहरण) लगभग बाद में है - लेकिन एक बड़ी समस्या के साथ: यह केवल वर्तमान निर्देशिका के ग्लोब के लिए काम करता है। मैं फ़ाइलों की पूरी तरह से मनमानी सूचियों में हेरफेर करने में सक्षम होना चाहता हूं; उदाहरण के लिए "ढूंढें" का आउटपुट, जो निर्देशिकाओं को दोबारा सूचीबद्ध करता है, या किसी अन्य सूची को सूचीबद्ध करता है। यदि मेरी सूची थी तो: (/tmp/foo.jpg | /home/alice/bar.jpg |/home/bob/my holiday/baz.jpg | /tmp/new\nline/grault.jpg), या फ़ाइलों की किसी अन्य पूरी तरह से मनमानी सूची (बेशक, संभावित रूप से उन जगहों और न्यूलाइनों के साथ)? – Idris

7

शायद तुम xargs लिए देख रहे हैं:

find . -print0 | xargs -r0 do_something_useful 

विकल्प एल 1 भी आप के लिए उपयोगी हो सकता है, जो केवल 1 फ़ाइल तर्क के साथ xargs कार्यकारी do_something_useful बनाता है।

+2

यह काफी नहीं है जो मैं बाद में था, क्योंकि सूची के साथ सरणी जैसी चीजों को करने का कोई मौका नहीं है, जैसे कि सॉर्टिंग: आपको प्रत्येक तत्व का उपयोग तब करना चाहिए जब यह खोज कमांड से बाहर दिखाई देता है। यदि आप इस उदाहरण पर विस्तृत कर सकते हैं, तो "do_something_useful" भाग एक बैश सरणी-पुश ऑपरेशन होने के साथ, तो यह हो सकता है कि मैं बाद में हूं। – Idris

+0

यह वही है जो मैं सबसे अधिक उपयोग करता हूं यदि मैं बाद में सरल हूं;) –

1

आप सुरक्षित रूप से इस के साथ गिनती कर सकते हैं:

find . -exec echo ';' | wc -l 

(यह हर फ़ाइल/dir पाया के लिए एक नई पंक्ति प्रिंट, और फिर नई-पंक्तियों मुद्रित गिनती ...)

0

यह समान है Stephan202 के संस्करण के लिए, लेकिन फ़ाइलों (और निर्देशिका) एक बार में एक सरणी में डाल दिया जाता है।for पाश यहाँ सिर्फ "उपयोगी बातें करते हैं" करने के लिए है:

echo ${#files[@]} 
3

फिर भी एक और गिनती फ़ाइलों का रास्ता:

files=(*)      # put files in current directory into an array 
i=0 
for file in "${files[@]}" 
do 
    echo "File ${i}: ${file}" # do something useful 
    let i++ 
done 

गिनती प्राप्त करने के लिए

find /DIR -type f -print0 | tr -dc '\0' | wc -c 
1

xargs से बचें अगर आप कर सकते हैं:

man ruby | less -p 777 
IFS=$'\777' 
#array=($(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' \; 2>/dev/null)) 
array=($(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' + 2>/dev/null)) 
echo ${#array[@]} 
printf "%s\n" "${array[@]}" | nl 
echo "${array[0]}" 
IFS=$' \t\n' 
+0

आप आईएफएस को '777' क्यों सेट करते हैं? – sschober

1

मैं नया हूं लेकिन मुझे विश्वास है कि यह एक उत्तर है; आशा है कि यह मदद करता है किसी को:

STYLE="$HOME/.fluxbox/styles/" 

declare -a array1 

LISTING=`find $HOME/.fluxbox/styles/ -print0 -maxdepth 1 -type f` 


echo $LISTING 
array1=(`echo $LISTING`) 
TAR_SOURCE=`echo ${array1[@]}` 

#tar czvf ~/FluxieStyles.tgz $TAR_SOURCE 
5

मुख्य समस्या यह है कि सीमांकक NUL (\ 0) बेकार यहाँ है, क्योंकि यह संभव आईएफएस एक NUL-मूल्य निर्दिष्ट नहीं है। इसलिए अच्छे प्रोग्रामर के रूप में हम परवाह करते हैं, कि हमारे कार्यक्रम के लिए इनपुट कुछ ऐसा है जो इसे संभालने में सक्षम है।

पहले हम एक छोटे से कार्यक्रम है, जो हमारे लिए इस हिस्से करता है बनाने के लिए:

#!/bin/bash 
printf "%s" "[email protected]" | base64 

... और यह base64str फोन (chmod मत भूलना + x)

दूसरा अब हम उपयोग कर सकते हैं एक सरल और के लिए लूप सीधा:

for i in `find -type f -exec base64str '{}' \;` 
do 
    file="`echo -n "$i" | base64 -d`" 
    # do something with file 
done 

तो चाल है, एक बेस 64-स्ट्रिंग कोई संकेत नहीं है जो पार्टी के लिए मुसीबत का कारण बनता है है - निश्चित रूप से एक XXD या कुछ इसी तरह भी काम कर सकते हैं।

+1

किसी को यह सुनिश्चित करना होगा कि प्रसंस्करण प्रतीत होने वाली फाइल सिस्टम का हिस्सा तब तक नहीं बदला जाता जब तक स्क्रिप्ट पूर्ण नहीं हो जाती है। यदि यह मामला नहीं है, तो दौड़ की स्थिति के परिणाम, जिनका गलत फाइलों पर कमांड का आह्वान करने के लिए उपयोग किया जा सकता है। उदाहरण के लिए एक निर्देशिका को हटाया जाना चाहिए (कहें/tmp/junk) को एक अप्रतिबंधित उपयोगकर्ता द्वारा/symlink/home द्वारा प्रतिस्थापित किया जा सकता है। यदि खोज कमांड रूट के रूप में चल रहा था, और यह पाया गया था- टाइप d -exec rm -rf '{}' \ ;, यह सभी उपयोगकर्ताओं के होम फ़ोल्डर्स को हटा देगा। – Demi

+1

'read -r -d' '' अगले एनयूएल तक सब कुछ "$ REPLY" में पढ़ेगा। 'आईएफएस 'की परवाह करने की कोई ज़रूरत नहीं है। –

+1

इसके अलावा, http://mywiki.wooledge.org/BashPitfalls –

-1

बैश फ़ाइल नामों (या वास्तव में कोई भी पाठ) को संभालने में कभी अच्छा नहीं रहा है क्योंकि यह रिक्त स्थान को सूची डिलीमीटर के रूप में उपयोग करता है।

मैं इसके बजाय sh लाइब्रेरी के साथ पायथन का उपयोग करने की सलाह दूंगा।

+3

में प्रविष्टि # 1 देखें, आप बस के बारे में पूरी तरह गलत हैं। –

+1

और फिर भी, वह नहीं है। –

0

पुराना सवाल, लेकिन किसी ने भी इस सरल विधि का सुझाव नहीं दिया, इसलिए मैंने सोचा कि मैं चाहता हूं। अनुमोदित है कि आपके फ़ाइल नामों में ईटीएक्स है, यह आपकी समस्या का समाधान नहीं करता है, लेकिन मुझे संदेह है कि यह किसी वास्तविक दुनिया परिदृश्य के लिए कार्य करता है।शून्य का उपयोग करने की कोशिश करने से डिफ़ॉल्ट आईएफएस हैंडलिंग नियमों का सामना करना पड़ता है। ढूंढने के विकल्पों और त्रुटि प्रबंधन के साथ अपने स्वाद का मौसम।

savedFS="$IFS" 
IFS=$'\x3' 
filenames=(`find wherever -printf %p$'\x3'`) 
IFS="$savedFS" 
+0

मतलब ** ईटीएक्स ** क्या है? शायद फ़ाइल नाम ** EXT ** आयन या शायद [** पाठ का अंत **] (http://www.abbreviations.com/ETX) ... – olibre

0

गॉर्डन डेविसन का जवाब बैश के लिए बहुत अच्छा है। हालांकि एक उपयोगी शॉर्टकट zsh उपयोगकर्ताओं के लिए मौजूद हैं:

सबसे पहले, एक चर में आप स्ट्रिंग जगह:

A="$(find /tmp -type f -print0)" 

इसके बाद, इस चर विभाजित है और एक सरणी में संग्रहीत:

B=(${(s/^@/)A}) 

नहीं है एक चाल: ^@ एनयूएल चरित्र है। ऐसा करने के लिए, आपको Ctrl + V को Ctrl + @ के बाद टाइप करना होगा।

आप $ बी के प्रत्येक प्रविष्टि की जांच कर सकते सही मूल्य में शामिल है:

for i in "$B[@]"; echo \"$i\" 

सावधान पाठकों है कि कॉल करने के लिए देख सकते हैं find आदेश ** सिंटैक्स का उपयोग ज्यादातर मामलों में बचा जा सकता है। उदाहरण के लिए:

B=(/tmp/**) 
0

बैश 4.4 के बाद से, निर्मित mapfile-d स्विच है (एक परिसीमक, read बयान के -d स्विच करने के लिए इसी निर्दिष्ट करने के लिए), और सीमांकक अशक्त बाइट हो सकता है। इसलिए, शीर्षक

एक बैश सरणी

में find . -print0 के उत्पादन पर कब्जा करने में प्रश्न के लिए एक अच्छा जवाब है:

mapfile -d '' ary < <(find . -print0) 
संबंधित मुद्दे