bash: व्हॉट्सएप-सुरक्षित प्रक्रियात्मक उपयोग का चयन करें


12

इन फ़ाइल नामों को देखते हुए:

$ ls -1
file
file name
otherfile

bash खुद एम्बेडेड व्हाट्सएप के साथ पूरी तरह से ठीक है:

$ for file in *; do echo "$file"; done
file
file name
otherfile
$ select file in *; do echo "$file"; done
1) file
2) file name
3) otherfile
#?

हालाँकि, कभी-कभी मैं हर फ़ाइल के साथ, या यहाँ तक कि सख्ती से काम नहीं करना चाहता हूँ $PWD, जहाँ वह findआता है। जो मुख्य रूप से व्हाट्सएप भी संभालता है:

$ find -type f -name file\*
./file
./file name
./directory/file
./directory/file name

मैं इस स्क्रिप्टलेट का एक पूर्ण-सुरक्षित संस्करण बनाने की कोशिश कर रहा हूं, जो इसका आउटपुट लेगा findऔर इसमें प्रस्तुत करेगा select:

$ select file in $(find -type f -name file); do echo $file; break; done
1) ./file
2) ./directory/file

हालाँकि, यह फ़ाइल नाम में व्हॉट्सएप के साथ विस्फोट करता है:

$ select file in $(find -type f -name file\*); do echo $file; break; done
1) ./file        3) name          5) ./directory/file
2) ./file        4) ./directory/file  6) name

आमतौर पर, मैं इसके साथ खिलवाड़ करके इसे प्राप्त करूंगा IFS। हालाँकि:

$ IFS=$'\n' select file in $(find -type f -name file\*); do echo $file; break; done
-bash: syntax error near unexpected token `do'
$ IFS='\n' select file in $(find -type f -name file\*); do echo $file; break; done
-bash: syntax error near unexpected token `do'

इसका क्या हल है?



1
यदि आप केवलfind किसी विशेष फ़ाइल नाम से मेल खाने की क्षमता के लिए उपयोग कर रहे हैं , तो आप बस select file in **/file*(सेटिंग के बाद shopt -s globstar) bash4 या उसके बाद का उपयोग कर सकते हैं ।
शेपनर

जवाबों:


14

यदि आपको केवल रिक्त स्थान और टैब को संभालने की आवश्यकता है (एम्बेडेड नईलाइन नहीं) तो आप किसी सरणी में पढ़ने के लिए mapfile(या इसके पर्यायवाची readarray) का उपयोग कर सकते हैं , जैसे दिए गए

$ ls -1
file
other file
somefile

फिर

$ IFS= mapfile -t files < <(find . -type f)
$ select f in "${files[@]}"; do ls "$f"; break; done
1) ./file
2) ./somefile
3) ./other file
#? 3
./other file

यदि आप करते हैं संभाल नई-पंक्तियों की जरूरत है, और अपने bashसंस्करण एक अशक्त-सीमांकित प्रदान करता है mapfile1 , तो आप उस के लिए संशोधित कर सकते हैं IFS= mapfile -t -d '' files < <(find . -type f -print0)। अन्यथा, लूप findका उपयोग करते हुए नल-सीमांकित आउटपुट से एक समान सरणी को इकट्ठा करें read:

$ touch $'filename\nwith\nnewlines'
$ 
$ files=()
$ while IFS= read -r -d '' f; do files+=("$f"); done < <(find . -type f -print0)
$ 
$ select f in "${files[@]}"; do ls "$f"; break; done
1) ./file
2) ./somefile
3) ./other file
4) ./filename
with
newlines
#? 4
./filename?with?newlines

1-d विकल्प को जोड़ा गया है mapfileमें bashसंस्करण 4.4 iirc


2
एक और क्रिया के लिए +1 जिसका मैंने पहले इस्तेमाल नहीं किया है
रोआमा

वास्तव में, mapfileमेरे लिए भी एक नया है। कुडोस।
डोपघोटी

while IFS= readसंस्करण बैश v3 (जो हम में से उन MacOS उपयोग करने के लिए महत्वपूर्ण है) में वापस काम करता है।
गॉर्डन डेविसन

3
find -print0वेरिएंट के लिए +1 ; एक ज्ञात-गलत संस्करण के बाद इसे डालने के लिए गड़गड़ाहट करें , और इसे केवल उपयोग के लिए वर्णन करें यदि कोई जानता है कि उन्हें नए समाचारों को संभालने की आवश्यकता है। यदि कोई अप्रत्याशित स्थानों को केवल उसी स्थान पर संभालता है जहाँ इसकी अपेक्षा की जाती है, तो कोई भी कभी भी अप्रत्याशित को नहीं संभाल पायेगा।
चार्ल्स डफी

8

इस उत्तर में किसी भी प्रकार की फ़ाइलों के लिए समाधान हैं । न्यूलाइन्स या स्पेस के साथ।
हाल ही में बाश के साथ-साथ प्राचीन बाश और यहां तक ​​कि पुराने पॉश गोले के समाधान भी हैं।

इस उत्तर में नीचे सूचीबद्ध पेड़ [1] का उपयोग परीक्षणों के लिए किया जाता है।

चुनते हैं

selectकिसी सरणी के साथ काम करना आसान है :

$ dir='deep/inside/a/dir'
$ arr=( "$dir"/* )
$ select var in "${arr[@]}"; do echo "$var"; break; done

या स्थितिगत मापदंडों के साथ:

$ set -- "$dir"/*
$ select var; do echo "$var"; break; done

तो, एकमात्र वास्तविक समस्या एक सरणी के अंदर या स्थितीय पैरामीटर के अंदर "फ़ाइलों की सूची" (सही ढंग से सीमांकित) प्राप्त करना है। पढ़ते रहिये।

दे घुमा के

मैं आपको बैश के साथ रिपोर्ट करने वाली समस्या नहीं देखता। बैश किसी दिए गए निर्देशिका के अंदर खोज करने में सक्षम है:

$ dir='deep/inside/a/dir'
$ printf '<%s>\n' "$dir"/*
<deep/inside/a/dir/directory>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>

या, अगर आपको एक लूप पसंद है:

$ set -- "$dir"/*
$ for f; do printf '<%s>\n' "$f"; done
<deep/inside/a/dir/directory>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>

ध्यान दें कि ऊपर दिया गया सिंटैक्स किसी भी (उचित) शेल (कम से कम csh नहीं) के साथ सही ढंग से काम करेगा।

एकमात्र सीमा जो ऊपर सिंटैक्स है, वह अन्य निर्देशिकाओं में उतरना है।
लेकिन मार सकता है:

$ shopt -s globstar
$ set -- "$dir"/**/*
$ for f; do printf '<%s>\n' "$f"; done
<deep/inside/a/dir/directory>
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/directory/file name>
<deep/inside/a/dir/directory/file with a
newline>
<deep/inside/a/dir/directory/zz last file>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>

केवल कुछ फ़ाइलों का चयन करने के लिए (जैसे कि फ़ाइल में समाप्त होने वाले ) सिर्फ * बदलें:

$ set -- "$dir"/**/*file
$ printf '<%s>\n' "$@"
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/directory/zz last file>
<deep/inside/a/dir/file>
<deep/inside/a/dir/zz last file>

मजबूत

जब आप शीर्षक में "स्थान- सुरक्षित " रखते हैं, तो मैं यह मानने जा रहा हूं कि आपका क्या मतलब था " मजबूत "।

रिक्त स्थान (या newlines) के बारे में मजबूत होने का सबसे सरल तरीका इनपुट के प्रसंस्करण को अस्वीकार करना है जिसमें रिक्त स्थान (या newlines) है। शेल में ऐसा करने का एक बहुत ही सरल तरीका एक त्रुटि के साथ बाहर निकलना है यदि कोई फ़ाइल नाम किसी स्थान के साथ फैलता है। ऐसा करने के कई तरीके हैं, लेकिन सबसे अधिक कॉम्पैक्ट (और पॉज़िक्स) (लेकिन एक डायरेक्टरी कंटेंट तक सीमित है, जिसमें suddirectories नाम और डॉट-फाइल्स से बचना शामिल है):

$ set -- "$dir"/file*                            # read the directory
$ a="$(printf '%s' "$@" x)"                      # make it a long string
$ [ "$a" = "${a%% *}" ] || echo "exit on space"  # if $a has an space.
$ nl='
'                    # define a new line in the usual posix way.  

$ [ "$a" = "${a%%"$nl"*}" ] || echo "exit on newline"  # if $a has a newline.

यदि उपयोग किए गए समाधान में से किसी भी आइटम में मजबूत है, तो परीक्षण को हटा दें।

बाश में, उप-निर्देशिकाओं का परीक्षण एक साथ किया जा सकता है ** ऊपर बताया गया है।

डॉट फाइलें शामिल करने के कुछ तरीके हैं, Posix समाधान है:

set -- "$dir"/* "$dir"/.[!.]* "$dir"/..?*

खोज

यदि खोज किसी कारण से उपयोग की जानी चाहिए, तो सीमांकक को NUL (0x00) से बदलें।

बैश 4.4+

$ readarray -t -d '' arr < <(find "$dir" -type f -name file\* -print0)
$ printf '<%s>\n' "${arr[@]}"
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/directory/file name>
<deep/inside/a/dir/directory/file with a
newline>
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/file>

bash 2.05+

i=1  # lets start on 1 so it works also in zsh.
while IFS='' read -d '' val; do 
    arr[i++]="$val";
done < <(find "$dir" -type f -name \*file -print0)
printf '<%s>\n' "${arr[@]}"

POSIXLY

एक वैध POSIX समाधान बनाने के लिए जहां पाते हैं कि एक NUL सीमांकक नहीं है और पढ़ने के लिए कोई नहीं -d(न ही -a) है, हमें पूरी तरह से अलग अनुलोम की आवश्यकता है।

हमें -execकॉल से कॉल करने के लिए एक कॉम्प्लेक्स का उपयोग करने की आवश्यकता है :

find "$dir" -type f -exec sh -c '
    for f do
        echo "<$f>"
    done
    ' sh {} +

या, अगर जरूरत है तो एक चयन (चयन बकवास का हिस्सा है, श नहीं):

$ find "$dir" -type f -exec bash -c '
      select f; do echo "<$f>"; break; done ' bash {} +

1) deep/inside/a/dir/file name
2) deep/inside/a/dir/zz last file
3) deep/inside/a/dir/file with a
newline
4) deep/inside/a/dir/directory/file name
5) deep/inside/a/dir/directory/zz last file
6) deep/inside/a/dir/directory/file with a
newline
7) deep/inside/a/dir/directory/file
8) deep/inside/a/dir/file
#? 3
<deep/inside/a/dir/file with a
newline>

[१] यह वृक्ष (\ ०१२ नए हैं):

$ tree
.
└── deep
    └── inside
        └── a
            └── dir
                ├── directory
                   ├── file
                   ├── file name
                   └── file with a \012newline
                ├── file
                ├── file name
                ├── otherfile
                ├── with a\012newline
                └── zz last file

इस दो कमांड के साथ बनाया जा सकता है:

$ mkdir -p deep/inside/a/dir/directory/
$ touch deep/inside/a/dir/{,directory/}{file{,\ {name,with\ a$'\n'newline}},zz\ last\ file}

6

आप लूपिंग कंस्ट्रक्शन के सामने एक वैरिएबल सेट नहीं कर सकते हैं, लेकिन आप इसे कंडीशन के सामने सेट कर सकते हैं। यहाँ आदमी पृष्ठ से खंड है:

किसी भी साधारण कमांड या फ़ंक्शन के लिए वातावरण को पैरामीटर असाइनमेंट के साथ इसे पूर्वनिर्मित करके अस्थायी रूप से संवर्धित किया जा सकता है, जैसा कि PARAMETERS में ऊपर वर्णित है।

(लूप एक साधारण कमांड नहीं है ।)

यहां विफलता और सफलता परिदृश्यों का प्रदर्शन करने के लिए आमतौर पर इस्तेमाल किया जाने वाला निर्माण है:

IFS=$'\n' while read -r x; do ...; done </tmp/file     # Failure
while IFS=$'\n' read -r x; do ...; done </tmp/file     # Success

दुर्भाग्य से मैं निर्माण IFSमें एक बदलाव को एम्बेड करने का तरीका नहीं देख सकता , selectजबकि यह एक संबद्ध के प्रसंस्करण को प्रभावित करता है $(...)। हालाँकि, IFSलूप के बाहर सेट होने से रोकने के लिए कुछ भी नहीं है :

IFS=$'\n'; while read -r x; do ...; done </tmp/file    # Also success

और यह ऐसा निर्माण है जिसके साथ मैं काम देख सकता हूं select:

IFS=$'\n'; select file in $(find -type f -name 'file*'); do echo "$file"; break; done

जब बचाव की मुद्रा में कोड लिखने मैं सलाह देते हैं कि खंड या तो एक subshell में चलाए जा, या IFSऔर SHELLOPTSबचाया और ब्लॉक के चारों ओर बहाल:

OIFS="$IFS" IFS=$'\n'                     # Split on newline only
OSHELLOPTS="$SHELLOPTS"; set -o noglob    # Wildcards must not expand twice

select file in $(find -type f -name 'file*'); do echo $file; break; done

IFS="$OIFS"
[[ "$OSHELLOPTS" !~ noglob ]] && set +o noglob

5
यह मानते हुए कि IFS=$'\n'सुरक्षित है निराधार है। फिल्मनाम पूरी तरह से न्यूलाइन शाब्दिक शामिल करने में सक्षम हैं।
चार्ल्स डफी

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

2
उन फ़ाइलों की सफाई करने वाले शेल स्क्रिप्ट का निर्माण करने वाले लोगों ने बोली लगाने की जहमत नहीं उठाई क्योंकि नाम "संभवतः मेल खाने में विफल" हो सकते हैं [0-9a-f]{24}। ग्राहक बिलिंग का समर्थन करने के लिए उपयोग किए जाने वाले डेटा का बैकअप टीबी खो गया था।
चार्ल्स डफी

4
@CharlesDuffy से पूरी तरह सहमत हैं। जब आप अंतःक्रियात्मक रूप से काम कर रहे हों और आप जो कर रहे हैं उसे देख सकते हैं तो किनारे के मामलों को संभालना ठीक नहीं है । selectइसके बहुत ही डिज़ाइन स्क्रिप्टेड समाधानों के लिए है, इसलिए इसे हमेशा किनारे के मामलों को संभालने के लिए डिज़ाइन किया जाना चाहिए।
वाइल्डकार्ड

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

4

मैं यहाँ अपने अधिकार क्षेत्र से बाहर हो सकता हूँ, लेकिन शायद आप कुछ इस तरह से शुरू कर सकते हैं, कम से कम इसे व्हाट्सएप से कोई परेशानी नहीं है:

find -maxdepth 1 -type f -printf '%f\000' | {
    while read -d $'\000'; do
            echo "$REPLY"
            echo
    done
}

किसी भी संभावित गलत धारणा से बचने के लिए, जैसा कि टिप्पणियों में उल्लेख किया गया है, ध्यान रखें कि उपरोक्त कोड इसके बराबर है:

   find -maxdepth 1 -type f -printf '%f\0' | {
        while read -d ''; do
                echo "$REPLY"
                echo
        done
    }

read -dएक चतुर समाधान है; इसके लिए धन्यवाद।
डोपघोटी

2
read -d $'\000'है वास्तव में समान करने के लिए read -d ''है, लेकिन पार्टी की क्षमताओं (जिसका अर्थ है, गलत तरीके से, कि यह तार के भीतर शाब्दिक NULs प्रतिनिधित्व करने में सक्षम है) के बारे में लोगों को गुमराह करने के लिए। भागो s1=$'foo\000bar'; s2='foo', और फिर दोनों मूल्यों के बीच अंतर करने का एक तरीका खोजने का प्रयास करें। (भंडारित मूल्य के समतुल्य बनाकर एक भावी संस्करण कमांड प्रतिस्थापन व्यवहार के साथ सामान्य हो सकता है foobar, लेकिन आज ऐसा नहीं है)।
चार्ल्स डफी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.