निर्देशिका में फ़ाइलों पर लूप कैसे करें और पथ बदलें और फ़ाइल नाम में प्रत्यय जोड़ें


562

मुझे एक स्क्रिप्ट लिखने की ज़रूरत है जो मेरे कार्यक्रम को विभिन्न तर्कों के साथ शुरू करती है, लेकिन मैं बैश पर नया हूं। मैं अपना कार्यक्रम शुरू करता हूं:

./MyProgram.exe Data/data1.txt [Logs/data1_Log.txt]

यहाँ मैं क्या करना चाहता हूँ के लिए छद्मकोड है:

for each filename in /Data do
  for int i = 0, i = 3, i++
    ./MyProgram.exe Data/filename.txt Logs/filename_Log{i}.txt
  end for
end for

इसलिए मैं वास्तव में हैरान हूं कि पहले से दूसरा तर्क कैसे बनाया जाए, इसलिए यह dataABCD_Log1.txt जैसा दिखता है और मेरा कार्यक्रम शुरू करता है।

जवाबों:


736

पहले नोटों की एक जोड़ी: जब आप Data/data1.txtएक तर्क के रूप में उपयोग करते हैं , तो क्या यह वास्तव में होना चाहिए /Data/data1.txt(एक प्रमुख स्लैश के साथ)? इसके अलावा, बाहरी लूप केवल .txt फ़ाइलों, या सभी फ़ाइलों के लिए / डेटा स्कैन करना चाहिए? यहाँ /Data/data1.txtकेवल एक जवाब है, और .txt फाइलें

#!/bin/bash
for filename in /Data/*.txt; do
    for ((i=0; i<=3; i++)); do
        ./MyProgram.exe "$filename" "Logs/$(basename "$filename" .txt)_Log$i.txt"
    done
done

टिप्पणियाँ:

  • /Data/*.txt/ ( डेटा / डेटा भाग सहित ) में पाठ फ़ाइलों के पथ के लिए फैलता है
  • $( ... ) एक शेल कमांड चलाता है और कमांड लाइन में उस बिंदु पर अपना आउटपुट सम्मिलित करता है
  • basename somepath .txtकुछपथ के आधार भाग को आउटपुट करता है, .txt को अंत से हटा दिया जाता है (जैसे /Data/file.txt-> file)

यदि आपको Data/file.txtइसके बजाय MyProgram चलाने की आवश्यकता है /Data/file.txt, "${filename#/}"तो प्रमुख स्लैश को हटाने के लिए उपयोग करें। दूसरी ओर, यदि यह वास्तव में आप स्कैन Dataनहीं /Dataकरना चाहते हैं, तो बस उपयोग करें for filename in Data/*.txt


1
जाहिरा तौर basename -sपर एक गैर-मानक एक्सटेंशन है - मैं मानक सिंटैक्स का उपयोग करने के लिए अपने उत्तर को संपादित करूंगा।
गॉर्डन डेविसन

21
यदि कोई फाइल नहीं मिलती है / वाइल्डकार्ड से मेल खाता हूं, तो मैं लूप निष्पादित ब्लॉक के लिए ढूंढ रहा हूं, फिर भी फ़ाइल नाम = "/Data/*.txt" के साथ एक बार दर्ज किया गया है। इससे कैसे बचा जा सकता है?
ओलिवर पियरमैन

20
@ ऑलिवरप्रीमैन या तो shopt -s nullglobलूप से पहले उपयोग करें (और shopt -u nullglobबाद में समस्याओं से बचने के लिए), या if [[ ! -e "$filename ]]; then continue; fiलूप की शुरुआत में जोड़ें , इसलिए यह बिना किसी फाइल को छोड़ देगा।
गॉर्डन डेविसन

9
यह तब काम नहीं करता है जब ऐसी फाइलें हों जिनमें उनके नाम पर व्हॉट्सएप हो।
ईसा हसन

4
@ आईसा यह व्हाट्सएप के साथ काम करना चाहिए, जब तक कि सभी दोहरे उद्धरण चिह्नों के स्थान पर हैं। दोहरे उद्धरण चिह्नों में से किसी को भी छोड़ दें, और आपको व्हाट्सएप की समस्या होगी।
गॉर्डन डेविसन

344

धागे को नेक्रोमेंट करने के लिए क्षमा करें, लेकिन जब भी आप ग्लोबिंग करके फाइलों पर पुनरावृति करते हैं, तो कोने के मामले से बचने के लिए यह अच्छा अभ्यास है जहां ग्लोब मैच नहीं करता है (जो लूप वेरिएबल को (अन-मैचिंग) ग्लोब पैटर्न स्ट्रिंग में ही विस्तार करता है)।

उदाहरण के लिए:

for filename in Data/*.txt; do
    [ -e "$filename" ] || continue
    # ... rest of the loop body
done

संदर्भ: बैश नुकसान


8
यह अभी भी एक समय पर चेतावनी है। मुझे लगा कि मैंने अपनी स्क्रिप्ट गलत तरीके से बनाई है, लेकिन मेरे पास ऊपरी मामले के बजाय मेरा फाइल एक्सटेंशन लोअर केस था, इसमें कोई फाइल नहीं मिली, और ग्लोब पैटर्न लौटा दिया। ओह।
RufusVS

5
चूंकि बैश टैग का उपयोग किया जाता है: यह वही shopt nullglobहै जो है! (या shopt failglobइच्छित व्यवहार के आधार पर भी उपयोग किया जा सकता है)।
गनीउर्फ_निगियॉर्फ

इसके अलावा, यह प्रसंस्करण के दौरान हटाई गई फ़ाइलों की भी जांच करता है, इससे पहले कि लूप उन तक पहुंच जाए।
लोक्सैक्स

1
उस मामले को भी संभालता है जहां आपके पास एक निर्देशिका हैdir.txt
vidstige

75
for file in Data/*.txt
do
    for ((i = 0; i < 3; i++))
    do
        name=${file##*/}
        base=${name%.txt}
        ./MyProgram.exe "$file" Logs/"${base}_Log$i.txt"
    done
done

name=${file##*/}प्रतिस्थापन ( खोल पैरामीटर विस्तार ) पिछले करने के लिए अग्रणी पथ नाम को हटा /

base=${name%.txt}प्रतिस्थापन अनुगामी को हटा .txt। यदि एक्सटेंशन अलग-अलग हो सकते हैं तो यह थोड़ा पेचीदा है।


3
मेरा मानना ​​है कि आपके कोड में कोई त्रुटि है। एक पंक्ति होना चाहिए base=${name%.txt}, बजाय base=${base%.txt}
केसकलीम

4
@ कैसी क्लिमकोव्स्की: हाँ; जब कोड और टिप्पणी असहमत हैं, तो उनमें से कम से कम एक गलत है। इस मामले में, मुझे लगता है कि यह केवल एक है - कोड; अक्सर, यह वास्तव में दोनों कि गलत हो रहा है। यह बात बताने के लिए धन्यवाद; मैंने इसे ठीक कर लिया है।
जोनाथन लेफ़लर

8

आप सुरक्षित रूप से निर्देशिका संरचनाओं पर पुनरावृति करने के लिए पढ़ने के साथ अशक्त अलग आउटपुट विकल्प का उपयोग कर सकते हैं।

#!/bin/bash
find . -type f -print0 | while IFS= read -r -d $'\0' file; 
  do echo "$file" ;
done

तो आपके मामले के लिए

#!/bin/bash
find . -maxdepth 1 -type f  -print0 | while IFS= read -r -d $'\0' file; do
  for ((i=0; i<=3; i++)); do
    ./MyProgram.exe "$file" 'Logs/'"`basename "$file"`""$i"'.txt'
  done
done

इसके साथ ही

#!/bin/bash
while IFS= read -r -d $'\0' file; do
  for ((i=0; i<=3; i++)); do
    ./MyProgram.exe "$file" 'Logs/'"`basename "$file"`""$i"'.txt'
  done
done < <(find . -maxdepth 1 -type f  -print0)

स्क्रिप्ट (प्रक्रिया) के वर्तमान दायरे में लूप चलाएगा और जरूरत पड़ने पर वैरिएबल सेट करने के लिए खोज के आउटपुट का उपयोग करने की अनुमति देगा


2
$'\0'लिखने का एक अजीब तरीका है ''। आप याद कर रहे हैं IFS=और -rस्विच करने के लिए read: आपका रीड स्टेटमेंट होना चाहिए IFS= read -rd '' file:।
gniourf_gniourf

मुझे लगता है कि कुछ को खोज करनी होगी $'\0'और कुछ स्टैक पॉइंट्स को चारों ओर फैलाना होगा । आपके द्वारा इंगित किए गए संपादन करने जा रहे हैं। वहां IFS=कोशिश न करने के क्या बुरे प्रभाव हैं, echo -e "ok \nok\0" | while read -d '' line; do echo -e "$line"; doneऐसा लगता नहीं है। इसके अलावा, मैं देख रहा हूँ अक्सर डिफ़ॉल्ट है, लेकिन यह क्या हो रहा है रोकता के लिए एक उदाहरण नहीं मिल सकता है।
जेम्स

4
IFS=यदि किसी स्थान के साथ फ़ाइल नाम समाप्त होता है, तो इसकी आवश्यकता होती है: प्रयास के साथ touch 'Prospero '(अनुगामी स्थान पर ध्यान दें)। -rफ़ाइल नाम में बैकस्लैश होने पर भी आपको स्विच की आवश्यकता है : इसके साथ प्रयास करें touch 'Prospero\n'
ganiourf_gniourf 23

-1

ऐसा लगता है कि आप एक विंडोज़ फ़ाइल (.exe) को निष्पादित करने का प्रयास कर रहे हैं, निश्चित रूप से आपको पावरशेल का उपयोग करना चाहिए। वैसे भी एक लिनक्स बैश शेल पर एक साधारण एक-लाइनर पर्याप्त होगा।

[/home/$] for filename in /Data/*.txt; do for i in {0..3}; do ./MyProgam.exe  Data/filenameLogs/$filename_log$i.txt; done done

या मारपीट में

#!/bin/bash

for filename in /Data/*.txt; 
   do
     for i in {0..3}; 
       do ./MyProgam.exe Data/filename.txt Logs/$filename_log$i.txt; 
     done 
 done
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.