रिक्त स्थान के साथ फ़ाइलों की सूची में बदलाव करें


201

मैं फ़ाइलों की सूची पर पुनरावृति करना चाहता हूं। यह सूची एक findआदेश का परिणाम है , इसलिए मैं आया:

getlist() {
  for f in $(find . -iname "foo*")
  do
    echo "File found: $f"
    # do something useful
  done
}

यह ठीक है सिवाय इसके कि यदि किसी फ़ाइल के नाम में रिक्तियाँ हैं:

$ ls
foo_bar_baz.txt
foo bar baz.txt

$ getlist
File found: foo_bar_baz.txt
File found: foo
File found: bar
File found: baz.txt

रिक्त स्थान पर विभाजन से बचने के लिए मैं क्या कर सकता हूं?


जवाबों:


253

आप शब्द-आधारित पुनरावृत्ति को पंक्ति-आधारित एक से बदल सकते हैं:

find . -iname "foo*" | while read f
do
    # ... loop body
done

31
यह बेहद साफ है। और मुझे लूप के साथ IFS को बदलने की तुलना में अच्छा लग रहा है
डेरिक

15
यह एक एकल फ़ाइल पथ को विभाजित करेगा जिसमें \ n शामिल है। ठीक है, वे आसपास नहीं होने चाहिए, लेकिन वे बनाए जा सकते हैं:touch "$(printf "foo\nbar")"
ओली सॉन्डर्स

4
इनपुट की किसी भी व्याख्या को रोकने के लिए (बैकस्लैश, लीडिंग और ट्रेलिंग व्हाट्सएप), IFS= while read -r fइसके बजाय उपयोग करें ।
mklement0

2
यह उत्तरfind कुछ समय के लूप के अधिक सुरक्षित संयोजन को दर्शाता है ।
moi

5
स्पष्ट की ओर इशारा करते हुए लगता है, लेकिन लगभग सभी सरल मामलों में, -execएक स्पष्ट लूप की तुलना में क्लीनर होने जा रहा है find . -iname "foo*" -exec echo "File found: {}" \;:। इसके अलावा, कई मामलों में आपको लगता है कि पिछले जगह ले सकता है \;के साथ +एक आदेश में फ़ाइलों के बहुत सारे डाल करने के लिए।
n

152

इसे पूरा करने के लिए कई व्यावहारिक तरीके हैं।

यदि आप अपने मूल संस्करण से निकटता से चिपकना चाहते हैं तो यह इस प्रकार किया जा सकता है:

getlist() {
        IFS=$'\n'
        for file in $(find . -iname 'foo*') ; do
                printf 'File found: %s\n' "$file"
        done
}

यह तब भी विफल हो जाएगा जब फ़ाइल नाम में शाब्दिक नई सुर्खियाँ हों, लेकिन रिक्त स्थान इसे नहीं तोड़ेंगे।

हालाँकि, IFS के साथ खिलवाड़ करना आवश्यक नहीं है। यहाँ ऐसा करने का मेरा पसंदीदा तरीका है:

getlist() {
    while IFS= read -d $'\0' -r file ; do
            printf 'File found: %s\n' "$file"
    done < <(find . -iname 'foo*' -print0)
}

यदि आपको < <(command)सिंटैक्स अपरिचित लगता है तो आपको प्रक्रिया प्रतिस्थापन के बारे में पढ़ना चाहिए । इस ओवर for file in $(find ...)का लाभ यह है कि रिक्त स्थान, newlines और अन्य वर्णों वाली फ़ाइलों को सही ढंग से संभाला जाता है। यह काम करता है क्योंकि findके साथ -print0एक का उपयोग करेगा null(उर्फ \0न्यू लाइन के विपरीत प्रत्येक फ़ाइल नाम के लिए टर्मिनेटर के रूप में) और,, अशक्त एक फ़ाइल नाम में एक कानूनी चरित्र नहीं है।

लगभग-बराबर संस्करण पर इसका लाभ

getlist() {
        find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
                printf 'File found: %s\n' "$file"
        done
}

क्या लूप के शरीर में कोई भी चर असाइनमेंट संरक्षित है। यही है, यदि आप whileऊपर के रूप में पाइप करते हैं, तो शरीर whileएक उप-भाग में है जो वह नहीं है जो आप चाहते हैं।

प्रक्रिया प्रतिस्थापन संस्करण का लाभ find ... -print0 | xargs -0कम से कम है: xargsसंस्करण ठीक है अगर आपको ज़रूरत है तो बस एक लाइन प्रिंट करना है या फ़ाइल पर एक ही ऑपरेशन करना है, लेकिन अगर आपको कई चरणों को निष्पादित करने की आवश्यकता है तो लूप संस्करण आसान है।

संपादित करें : यहां एक अच्छी परीक्षण स्क्रिप्ट है ताकि आप इस समस्या को हल करने के विभिन्न प्रयासों के बीच अंतर का अंदाजा लगा सकें

#!/usr/bin/env bash

dir=/tmp/getlist.test/
mkdir -p "$dir"
cd "$dir"

touch       'file not starting foo' foo foobar barfoo 'foo with spaces'\
    'foo with'$'\n'newline 'foo with trailing whitespace      '

# while with process substitution, null terminated, empty IFS
getlist0() {
    while IFS= read -d $'\0' -r file ; do
            printf 'File found: '"'%s'"'\n' "$file"
    done < <(find . -iname 'foo*' -print0)
}

# while with process substitution, null terminated, default IFS
getlist1() {
    while read -d $'\0' -r file ; do
            printf 'File found: '"'%s'"'\n' "$file"
    done < <(find . -iname 'foo*' -print0)
}

# pipe to while, newline terminated
getlist2() {
    find . -iname 'foo*' | while read -r file ; do
            printf 'File found: '"'%s'"'\n' "$file"
    done
}

# pipe to while, null terminated
getlist3() {
    find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
            printf 'File found: '"'%s'"'\n' "$file"
    done
}

# for loop over subshell results, newline terminated, default IFS
getlist4() {
    for file in "$(find . -iname 'foo*')" ; do
            printf 'File found: '"'%s'"'\n' "$file"
    done
}

# for loop over subshell results, newline terminated, newline IFS
getlist5() {
    IFS=$'\n'
    for file in $(find . -iname 'foo*') ; do
            printf 'File found: '"'%s'"'\n' "$file"
    done
}


# see how they run
for n in {0..5} ; do
    printf '\n\ngetlist%d:\n' $n
    eval getlist$n
done

rm -rf "$dir"

1
आपके उत्तर को स्वीकार किया: सबसे पूर्ण और दिलचस्प - मुझे $IFSऔर < <(cmd)वाक्यविन्यास के बारे में पता नहीं था । फिर भी एक बात बनी हुई है मेरे लिए अस्पष्ट, क्यों $में $'\0'? बहुत बहुत धन्यवाद।
gregseth

2
+1, लेकिन आपको while IFS= readव्हॉट्सएप के साथ शुरू या समाप्त होने वाली फ़ाइलों को संभालने के लिए ... को जोड़ना चाहिए ।
गॉर्डन डेविसन

1
प्रक्रिया प्रतिस्थापन समाधान के लिए एक चेतावनी है। यदि आपके पास लूप के अंदर कोई संकेत है (या किसी अन्य तरीके से एसटीडीआईएन से पढ़ रहे हैं), तो इनपुट आपके द्वारा लूप में फीड किए गए सामान से भर जाएगा। (शायद यह जवाब देने के लिए जोड़ा जाना चाहिए?)
andsens

2
@uvsmtid: इस प्रश्न को टैग किया गया था bashताकि मैं बैश-विशिष्ट सुविधाओं का उपयोग करके सुरक्षित महसूस करूँ। प्रक्रिया प्रतिस्थापन अन्य गोले के लिए पोर्टेबल नहीं है (स्वयं ही इस तरह के एक महत्वपूर्ण अद्यतन प्राप्त करने की संभावना नहीं है)।
sorpigal

2
के IFS=$'\n'साथ संयोजन forलाइन-आंतरिक शब्द-विभाजन को रोकता है, लेकिन फिर भी परिणामी लाइनों को ग्लोबिंग के अधीन बनाता है, इसलिए यह दृष्टिकोण पूरी तरह से मजबूत नहीं है (जब तक कि आप पहले ग्लोबिंग को बंद नहीं करते हैं)। जबकि read -d $'\0'काम करता है, यह थोड़ा में यह पता चलता है कि आप उपयोग कर सकते भ्रामक है $'\0'NULs बनाने के लिए - आप नहीं कर सकते: एक \0एक में एएनएसआई सी उद्धृत स्ट्रिंग को प्रभावी ढंग से समाप्त हो जाता है , स्ट्रिंग ताकि -d $'\0'प्रभावी रूप से रूप में ही है -d ''
mklement0

29

एक बहुत ही सरल उपाय भी है: बैश ग्लोबिंग पर भरोसा करें

$ mkdir test
$ cd test
$ touch "stupid file1"
$ touch "stupid file2"
$ touch "stupid   file 3"
$ ls
stupid   file 3  stupid file1     stupid file2
$ for file in *; do echo "file: '${file}'"; done
file: 'stupid   file 3'
file: 'stupid file1'
file: 'stupid file2'

ध्यान दें कि मुझे यकीन नहीं है कि यह व्यवहार डिफ़ॉल्ट है, लेकिन मुझे अपनी दुकान में कोई विशेष सेटिंग दिखाई नहीं दे रही है, इसलिए मैं जाकर कहूंगा कि यह "सुरक्षित" होना चाहिए (ओएक्सएक्स और उबंटू पर परीक्षण किया गया)।


13
find . -iname "foo*" -print0 | xargs -L1 -0 echo "File found:"

6
एक साइड नोट के रूप में, यह केवल तभी काम करेगा जब आप एक कमांड निष्पादित करना चाहते हैं। एक शेल बिलिन इस तरह से काम नहीं करेगा।
एलेक्स


6

चूंकि आप किसी अन्य प्रकार के फ़िल्टरिंग नहीं कर रहे हैं find, आप bash4.0 के रूप में निम्न का उपयोग कर सकते हैं :

shopt -s globstar
getlist() {
    for f in **/foo*
    do
        echo "File found: $f"
        # do something useful
    done
}

**/शून्य या अधिक निर्देशिकाओं से मेल खाते जाएगा, ताकि पूरा पैटर्न से मेल खाएगी foo*वर्तमान निर्देशिका या किसी भी उपनिर्देशिका में।


3

मैं वास्तव में छोरों और सरणी पुनरावृत्ति के लिए पसंद करता हूं, इसलिए मुझे लगता है कि मैं इस उत्तर को मिश्रण में जोड़ दूंगा ...

मुझे marchelbling की बेवकूफाना फ़ाइल उदाहरण भी पसंद आया। :)

$ mkdir test
$ cd test
$ touch "stupid file1"
$ touch "stupid file2"
$ touch "stupid   file 3"

परीक्षण निर्देशिका के अंदर:

readarray -t arr <<< "`ls -A1`"

यह प्रत्येक फ़ाइल लिस्टिंग लाइन को arrकिसी भी ट्रैशिंग न्यूलाइन के साथ नाम दिए गए बैश ऐरे में जोड़ता है।

आइए बताते हैं हम इन फाइलों को बेहतर नाम देना चाहते हैं ...

for i in ${!arr[@]}
do 
    newname=`echo "${arr[$i]}" | sed 's/stupid/smarter/; s/  */_/g'`; 
    mv "${arr[$i]}" "$newname"
done

$ {! आगमन [@]} 0 1 2 इतना करने के लिए फैलता है "$ {आगमन [$ i]}" मैं है वें सरणी के तत्व। चर के आसपास के उद्धरण रिक्त स्थान को संरक्षित करने के लिए महत्वपूर्ण हैं।

परिणाम तीन नामांकित फ़ाइलें हैं:

$ ls -1
smarter_file1
smarter_file2
smarter_file_3

2

findएक -execतर्क है जो खोज परिणामों पर लूप करता है और एक मनमाना कमांड निष्पादित करता है। उदाहरण के लिए:

find . -iname "foo*" -exec echo "File found: {}" \;

यहाँ {}पाया फ़ाइलों का प्रतिनिधित्व करता है, और यह ""फ़ाइल नाम में रिक्त स्थान से निपटने के लिए परिणामी शेल कमांड के लिए अनुमति देता है।

कई मामलों में आप उस अंतिम को बदल सकते हैं \;(जो एक नई कमांड शुरू करता है) \+, जो एक कमांड में कई फाइलें रखेगा (जरूरी नहीं कि उन सभी को एक बार में, हालांकि, man findअधिक विवरण के लिए देखें)।


0

कुछ मामलों में, यदि आपको फ़ाइलों की सूची को कॉपी या स्थानांतरित करने की आवश्यकता है, तो आप उस सूची को भी जाग्रत कर सकते हैं। फ़ील्ड
के \"" "\"चारों ओर महत्वपूर्ण $0(अपनी फ़ाइलों को संक्षिप्त में, एक पंक्ति-सूची = एक फ़ाइल)।

find . -iname "foo*" | awk '{print "mv \""$0"\" ./MyDir2" | "sh" }'

0

ठीक है - स्टैक ओवरफ्लो पर मेरी पहली पोस्ट!

हालाँकि, इसके साथ मेरी समस्याएं हमेशा csh में होती हैं, मैं जो समाधान प्रस्तुत करता हूँ, उसे मैं निश्चित नहीं करता, दोनों में काम करता हूँ। मुद्दा "एलएस" रिटर्न के शेल की व्याख्या के साथ है। हम *वाइल्डकार्ड के शेल विस्तार का उपयोग करके समस्या से "एलएस" को हटा सकते हैं - लेकिन यह "नो मैच" त्रुटि देता है यदि वर्तमान (या निर्दिष्ट फ़ोल्डर) में कोई फाइलें नहीं हैं - इसके लिए हम बस विस्तार करते हैं इस प्रकार डॉट-फाइल को शामिल करने के लिए विस्तार: * .*- यह फाइलों के बाद से हमेशा परिणाम देगा। और .. हमेशा मौजूद रहेगा। तो csh में हम इस निर्माण का उपयोग कर सकते हैं ...

foreach file (* .*)
   echo $file
end

यदि आप मानक डॉट-फाइल को फ़िल्टर करना चाहते हैं तो यह काफी आसान है ...

foreach file (* .*)
   if ("$file" == .) continue
   if ("file" == ..) continue
   echo $file
end

इस धागे पर पहली पोस्ट में कोड इस प्रकार लिखा जाएगा: -

getlist() {
  for f in $(* .*)
  do
    echo "File found: $f"
    # do something useful
  done
}

उम्मीद है की यह मदद करेगा!


0

नौकरी के लिए एक और उपाय ...

लक्ष्य था:

  • निर्देशिकाओं में पुनरावर्ती रूप से चयन / फ़िल्टर फ़ाइलनाम
  • प्रत्येक नाम को संभालें (पथ में जो कुछ भी हो ...)
#!/bin/bash  -e
## @Trick in order handle File with space in their path...
OLD_IFS=${IFS}
IFS=$'\n'
files=($(find ${INPUT_DIR} -type f -name "*.md"))
for filename in ${files[*]}
do
      # do your stuff
      #  ....
done
IFS=${OLD_IFS}



रचनात्मक टिप्पणी के लिए Thx, लेकिन: 1- यह एक वास्तविक समस्या है, 2- खोल समय में विकसित हो सकता है ... जैसा कि हर कोई मुझे लगता है; 3- ऊपर दिए गए उत्तर में से कोई भी समस्या को हल किए बिना या अस्वीकार किए गए pb के DIRECT रिज़ॉल्यूशन को संतुष्ट नहीं कर सकता है :-)
विंस B
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.