मैं बैश में एक सरणी के रूप में "खोज" कमांड परिणाम कैसे स्टोर कर सकता हूं


96

मैं परिणाम findको सरण के रूप में सहेजने का प्रयास कर रहा हूं । यहाँ मेरा कोड है:

#!/bin/bash

echo "input : "
read input

echo "searching file with this pattern '${input}' under present directory"
array=`find . -name ${input}`

len=${#array[*]}
echo "found : ${len}"

i=0

while [ $i -lt $len ]
do
echo ${array[$i]}
let i++
done

मुझे वर्तमान निर्देशिका के तहत 2 .txt फाइलें मिलती हैं। इसलिए मैं परिणाम के रूप में '2' की उम्मीद करता हूं ${len}। हालाँकि, यह 1 प्रिंट करता है। इसका कारण यह है कि यह findएक तत्व के रूप में सभी परिणाम लेता है । मैं इसे कैसे ठीक करूं?

PS
मुझे इसी तरह की समस्या के बारे में StackOverFlow पर कई समाधान मिले। हालाँकि, वे थोड़े अलग हैं इसलिए मैं अपने मामले में आवेदन नहीं कर सकता। मुझे लूप से पहले एक चर में परिणामों को संग्रहीत करने की आवश्यकता है। एक बार फिर धन्यवाद।

जवाबों:


137

लिनक्स उपयोगकर्ताओं के लिए अद्यतन 2020:

यदि आपके पास bash (4.4-अल्फ़ा या बेहतर) का अप-टू-डेट संस्करण है, जैसा कि आप शायद लिनक्स पर हैं, तो आप बेंजामिन डब्ल्यू के उत्तर का उपयोग करें ।

यदि आप Mac OS पर हैं, जिसे मैंने चेक किया था- तब भी bash 3.2 का उपयोग किया गया था, या अन्यथा पुराने बैश का उपयोग कर रहे हैं, तो अगले भाग पर जारी रखें।

बैश 4.3 या उससे पहले का उत्तर

सरणी findमें आउटपुट प्राप्त करने के लिए यहां एक समाधान है bash:

array=()
while IFS=  read -r -d $'\0'; do
    array+=("$REPLY")
done < <(find . -name "${input}" -print0)

यह मुश्किल है, क्योंकि सामान्य तौर पर, फ़ाइल नामों में रिक्त स्थान, नई लाइनें और अन्य स्क्रिप्ट-शत्रुतापूर्ण अक्षर हो सकते हैं। findफ़ाइल नामों को सुरक्षित रूप से उपयोग करने और एक-दूसरे से अलग करने का एकमात्र तरीका यह है कि उपयोग करने के लिए -print0फ़ाइल नामों को एक अशक्त चरित्र के साथ अलग किया जाए। यदि बैश readarray/ mapfileकार्यों ने अशक्त-पृथक तारों का समर्थन किया, तो यह बहुत असुविधा नहीं होगी, लेकिन वे नहीं करते हैं। बैश readकरता है और जो हमें ऊपर लूप में ले जाता है।

[यह जवाब मूल रूप से 2014 में लिखा गया था। यदि आपके पास बैश का हालिया संस्करण है, तो कृपया नीचे अपडेट देखें।]

यह काम किस प्रकार करता है

  1. पहली पंक्ति एक खाली सरणी बनाती है: array=()

  2. हर बार जब readकथन निष्पादित किया जाता है, तो मानक इनपुट से एक अशक्त-पृथक फ़ाइल नाम पढ़ा जाता है। -rविकल्प बताता readबैकस्लैश वर्ण अकेला छोड़ने के लिए। -d $'\0'बताता है readकि इनपुट अशक्त-अलग हो जाएगा। चूंकि हम नाम को छोड़ देते हैं read, शेल इनपुट को डिफ़ॉल्ट नाम में डालता है REPLY:।

  3. array+=("$REPLY")बयान सरणी के लिए नया फ़ाइल नाम संलग्न कर देता है array

  4. अंतिम पंक्ति लूप findके मानक इनपुट के आउटपुट प्रदान करने के लिए पुनर्निर्देशन और कमांड प्रतिस्थापन को जोड़ती है while

प्रक्रिया प्रतिस्थापन का उपयोग क्यों करें?

यदि हम प्रक्रिया प्रतिस्थापन का उपयोग नहीं करते हैं, तो लूप को इस प्रकार लिखा जा सकता है:

array=()
find . -name "${input}" -print0 >tmpfile
while IFS=  read -r -d $'\0'; do
    array+=("$REPLY")
done <tmpfile
rm -f tmpfile

ऊपर के आउटपुट findमें एक अस्थायी फ़ाइल में संग्रहीत किया जाता है और उस फ़ाइल का उपयोग मानक इनपुट के रूप में लूप में किया जाता है। प्रक्रिया प्रतिस्थापन का विचार ऐसी अस्थायी फ़ाइलों को अनावश्यक बनाने के लिए है। इसलिए, whileलूप के स्टाइन को प्राप्त करने के बजाय tmpfile, हम इसे स्टड से प्राप्त कर सकते हैं <(find . -name ${input} -print0)

प्रक्रिया प्रतिस्थापन व्यापक रूप से उपयोगी है। कई स्थानों पर जहां कोई कमांड किसी फ़ाइल से पढ़ना चाहता है , आप <(...)फ़ाइल नाम के बजाय , प्रक्रिया प्रतिस्थापन निर्दिष्ट कर सकते हैं । एक अनुरूप रूप है, >(...)जिसका उपयोग फ़ाइल नाम के स्थान पर किया जा सकता है जहां कमांड फ़ाइल में लिखना चाहता है ।

सरणियों की तरह, प्रक्रिया प्रतिस्थापन बैश और अन्य उन्नत गोले की एक विशेषता है। यह POSIX मानक का हिस्सा नहीं है।

वैकल्पिक: lastpipe

यदि वांछित है, तो lastpipeप्रक्रिया प्रतिस्थापन (हैट टिप: सीज़र ) के बजाय इस्तेमाल किया जा सकता है :

set +m
shopt -s lastpipe
array=()
find . -name "${input}" -print0 | while IFS=  read -r -d $'\0'; do array+=("$REPLY"); done; declare -p array

shopt -s lastpipeबश को वर्तमान शेल (पृष्ठभूमि नहीं) में पाइप लाइन में अंतिम कमांड चलाने के लिए कहता है। इस तरह, arrayपाइपलाइन पूरी होने के बाद अस्तित्व में रहता है। क्योंकि lastpipeजॉब कंट्रोल बंद होने पर ही हम प्रभावी होते हैं set +m। (स्क्रिप्ट में, कमांड लाइन के विपरीत, नौकरी नियंत्रण डिफ़ॉल्ट रूप से बंद है।)

अतिरिक्त नोट्स

निम्न आदेश शेल चर बनाता है, शेल सरणी नहीं:

array=`find . -name "${input}"`

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

array=(`find . -name "${input}"`)  # don't do this

समस्या यह है कि शेल शब्द विभाजन के परिणामों को निष्पादित करता है findताकि सरणी के तत्वों की गारंटी न हो कि आप क्या चाहते हैं।

अपडेट 2019

संस्करण 4.4-अल्फा के साथ शुरू, बैश अब एक -dविकल्प का समर्थन करता है ताकि उपरोक्त लूप अब आवश्यक न हो। इसके बजाय, एक का उपयोग कर सकते हैं:

mapfile -d $'\0' array < <(find . -name "${input}" -print0)

इस बारे में अधिक जानकारी के लिए कृपया बेंजामिन डब्ल्यू का जवाब देखें (और अपवोट करें) ।


1
@JuneyoungOh यह मदद की खुशी है। मैंने प्रक्रिया प्रतिस्थापन का एक खंड जोड़ा।
जॉन १०२४२४

3
@ रॉकलाइट एक अच्छा अवलोकन है लेकिन अधूरा है। हालांकि यह सच है कि हम कई शब्दों में विभाजित नहीं होते हैं, फिर भी हमें IFS=इनपुट लाइनों की शुरुआत या छोर से व्हॉट्सएप को हटाने से बचने की आवश्यकता है। आप के उत्पादन की तुलना द्वारा आसानी से इस परीक्षण कर सकते हैं read var <<<' abc '; echo ">$var<"के उत्पादन के साथ IFS= read var <<<' abc '; echo ">$var<"। पूर्व मामले में, पहले और बाद के रिक्त स्थान abcहटा दिए जाते हैं। उत्तरार्द्ध में, वे नहीं हैं। व्हॉट्सएप के साथ शुरू या समाप्त होने वाले फ़ाइल नाम असामान्य हो सकते हैं लेकिन, यह मौजूद है, हम चाहते हैं कि वे सही तरीके से संसाधित हों।
2:10 बजे जॉन 1024

1
नमस्ते, के बाद मैं अपने कोड को निष्पादित मैं अप्रत्याशित टोकन के पास संदेश वाक्यविन्यास त्रुटि प्राप्त <' किया <<(खोजने के लिए आ / / -newermt "$ last_build_timestamp_v" -type f -print0) ''
Przemysław Sienkiewicz

1
एक नोट: सरल ''का उपयोग इसके बजाय किया जा सकता है $'\0':n=0; while IFS= read -r -d '' line || [ "$line" ]; do echo "$((++n)):$line"; done < <(printf 'first\nstill first\0second\0third')
ग्लेन जैकमैन

1
@ मुझे लगता है कि आप लिखने का इरादा रखते हैं BLAH=$(find . -name '*.php')। जैसा कि उत्तर में चर्चा की गई है, यह दृष्टिकोण सीमित मामलों में काम करेगा, लेकिन यह सामान्य रूप से सभी फ़ाइल नाम के साथ काम नहीं करेगा और यह उत्पादन नहीं करता है, जैसा कि ओपी को उम्मीद है, एक सरणी
जॉन 1024

36

बैश 4.4 ने / के -dलिए एक विकल्प पेश किया , इसलिए इसे अब हल किया जा सकता हैreadarraymapfile

readarray -d '' array < <(find . -name "$input" -print0)

एक विधि के लिए जो मनमानी फ़ाइल नाम के साथ काम करता है जिसमें ब्लैंक, न्यूलाइन्स और ग्लोबिंग वर्ण शामिल हैं। इसके लिए आवश्यक है कि आपका findसमर्थन -print0, उदाहरण के लिए GNU खोज करता है।

से मैनुअल (अन्य विकल्पों को छोड़ते हुए):

mapfile [-d delim] [array]

-d
का पहला वर्ण delimनई इनपुट के बजाय प्रत्येक इनपुट लाइन को समाप्त करने के लिए उपयोग किया जाता है। यदि delimरिक्त स्ट्रिंग है, mapfileतो NUL वर्ण पढ़ते समय एक पंक्ति को समाप्त कर देगा।

और readarrayसिर्फ एक पर्यायवाची है mapfile


18

आप उपयोग कर रहे हैं bash4 या बाद में, आप अपने के उपयोग की जगह ले सकता findसाथ

shopt -s globstar nullglob
array=( **/*"$input"* )

**पैटर्न से सक्षम globstarमैचों 0 या अधिक निर्देशिका, पैटर्न वर्तमान निर्देशिका में एक मनमाना गहराई तक मैच के लिए अनुमति देता है। nullglobविकल्प के बिना , पैटर्न (पैरामीटर विस्तार के बाद) का शाब्दिक रूप से इलाज किया जाता है, इसलिए बिना किसी मिलान के आपके पास खाली सरणी के बजाय एक एकल स्ट्रिंग के साथ एक सरणी होगी।

dotglobपहली पंक्ति में विकल्प को भी जोड़ें यदि आप छिपी हुई निर्देशिकाओं (जैसे .ssh) को पार करना चाहते हैं और छिपी हुई फ़ाइलों (जैसे .bashrc) से भी मेल खाते हैं ।


4
शायद nullglobभी ...
कोजिरो

1
हाँ, मैं हमेशा यह भूल जाता हूँ।
चेपनर

5
ध्यान दें कि इसमें छिपी हुई फ़ाइलें और निर्देशिकाएं शामिल नहीं होंगी, जब तक dotglobकि सेट नहीं किया जाता है (यह वांछित नहीं हो सकता है या नहीं, लेकिन यह भी ध्यान देने योग्य है)।
गनीउर्फ़_ग्निउरफ़

10

आप कुछ इस तरह की कोशिश कर सकते हैं

array=(`find . -type f | sort -r | head -2`)
, और सरणी मानों को प्रिंट करने के लिए, आप इको जैसी कोई चीज़ आज़मा सकते हैं "${array[*]}"


8
जगह या ग्लोब पात्रों के साथ फ़ाइल नाम होने पर टूट जाता है।
gourourf_gniourf

2

निम्नलिखित MacOS पर Bash और Z Shell दोनों के लिए काम करता है।

#! /bin/sh

IFS=$'\n'
paths=($(find . -name "foo"))
unset IFS

printf "%s\n" "${paths[@]}"

यह उन रिक्त स्थान और अन्य विशेष वर्ण वाली फ़ाइलों के साथ काम करता है, जिनके नाम में एक लाइनब्रेक होने वाली (सम्मिलित रूप से दुर्लभ) मामलों की विफलता होती है। आप एक परीक्षण के साथ एक बना सकते हैंprintf "%b" "file name with spaces, a star * ...\012and a second line\0" | xargs -0 touch
स्टीफन गौरिचोन

शायद मैं यहां कुछ याद कर रहा हूं, लेकिन यह 99% मामलों के लिए बहुत स्पष्ट, आसान समाधान की तरह लगता है
मैट कोरोस्टॉफ़

-1

बैश में, $(<any_shell_cmd>)कमांड चलाने और आउटपुट कैप्चर करने में मदद करता है। को यह पासिंग IFSके साथ \nसीमांकक एक सरणी है कि परिवर्तित करने के लिए मदद करता है के रूप में।

IFS='\n' read -r -a txt_files <<< $(find /path/to/dir -name "*.txt")

4
यह findसरणी में परिणामों के केवल पहली फ़ाइल प्राप्त करेगा ।
बेंजामिन डब्ल्यू।

-2

आप इस तरह कर सकते हैं:

#!/bin/bash
echo "input : "
read input

echo "searching file with this pattern '${input}' under present directory"
array=(`find . -name '*'${input}'*'`)

for i in "${array[@]}"
do :
    echo $i
done

1
धन्यवाद। बहुत। लेकिन जैसा कि @ लुत्सेन ने बताया, फ़ाइलनाम में खाली जगहों को मेरे कार्यक्रम में माना जाना चाहिए। कोई बात नहीं धन्यवाद!
जुनेगॉन्ग ओह

-3

मेरे लिए, इसने साइबरविन पर ठीक काम किया:

declare -a names=$(echo "("; find <path> <other options> -printf '"%p" '; echo ")")
for nm in "${names[@]}"
do
    echo "$nm"
done

यह रिक्त स्थान के साथ काम करता है, लेकिन निर्देशिका नामों में दोहरे उद्धरण चिह्नों (") के साथ (जो वैसे भी विंडोज वातावरण में अनुमति नहीं है)।

-प्रिंट-ऑप्शन में स्पेस से सावधान रहें।


3
टूटा-फूटा और खतरनाक : उद्धरण नहीं संभालेगा, और मनमाने कोड इंजेक्शन के अधीन है। प्रयोग नहीं करें।
gourourf_gniourf

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

3
जैसा कि गनीफ ने बताया, यह उन वातावरणों के लिए नहीं है जहां अन्य आपके सिस्टम पर विकल्प दर्ज करते हैं, जैसे वेब पेज। लेकिन हर कोई उस वातावरण के लिए कार्यक्रम नहीं करता है। मैंने इसका उपयोग निर्देशिकाओं में फ़ाइलों के नाम बदलने के लिए किया है।
R Risack
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.