बैश शेल में कम से कम एक स्थान द्वारा अलग-अलग तारों में एक स्ट्रिंग को कैसे विभाजित किया जाए?


224

मेरे पास एक स्ट्रिंग है जिसमें प्रत्येक दो के बीच कम से कम एक स्थान के साथ कई शब्द हैं। मैं स्ट्रिंग को अलग-अलग शब्दों में कैसे विभाजित कर सकता हूं ताकि मैं उनके माध्यम से लूप कर सकूं?

स्ट्रिंग को एक तर्क के रूप में पारित किया जाता है। जैसे ${2} == "cat cat file"। मैं इसके माध्यम से कैसे लूप कर सकता हूं?

इसके अलावा, मैं कैसे जांच कर सकता हूं कि क्या स्ट्रिंग में स्थान हैं?


1
किस तरह का खोल? बैश, cmd.exe, शक्तियां ...?
एलेक्सी Sviridov

क्या आपको बस लूप करने की ज़रूरत है (उदाहरण के लिए प्रत्येक शब्द के लिए एक कमांड निष्पादित करें)? या क्या आपको बाद में उपयोग के लिए शब्दों की सूची संग्रहीत करने की आवश्यकता है?
DVK

जवाबों:


281

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

sentence="This is   a sentence."
for word in $sentence
do
    echo $word
done

 

This
is
a
sentence.

1
@MobRule - इसका एकमात्र दोष यह है कि आप आसानी से कैप्चर नहीं कर सकते हैं (कम से कम मुझे एक तरीका याद नहीं है) आगे की प्रक्रिया के लिए आउटपुट। STDOUT में सामान भेजने वाली चीज़ के लिए नीचे मेरा "tr" समाधान देखें
DVK

4
आप इसे केवल एक चर में जोड़ सकते हैं A=${A}${word}):।
लुकास जोन्स

1
$ टेक्स्ट सेट करें [यह शब्दों को $ 1, $ 2, $ 3 ... आदि में डाल देगा]
राजेश

32
वास्तव में यह ट्रिक न केवल एक गलत समाधान है, बल्कि शेल ग्लोबिंग के कारण भी बेहद खतरनाक है । अपेक्षित के बजाय touch NOPE; var='* a *'; for a in $var; do echo "[$a]"; doneआउटपुट (पठनीयता के लिए एसपीसी द्वारा प्रतिस्थापित एलएफ)। [NOPE] [a] [NOPE][*] [a] [*]
टीनो

@ यदि मुझे कुछ विशिष्ट स्ट्रिंग के आधार पर स्ट्रिंग को विभाजित करना है तो मुझे क्या करना चाहिए? उदाहरण ".xlsx" विभाजक।

296

व्यक्तिगत तत्वों तक पहुंचने में सक्षम होने के लिए मुझे एक सरणी में रूपांतरण पसंद है:

sentence="this is a story"
stringarray=($sentence)

अब आप सीधे व्यक्तिगत तत्वों तक पहुँच सकते हैं (यह 0 से शुरू होता है):

echo ${stringarray[0]}

या पाश में क्रम में वापस बदलने के लिए:

for i in "${stringarray[@]}"
do
  :
  # do whatever on $i
done

बेशक स्ट्रिंग के माध्यम से सीधे लूपिंग से पहले उत्तर दिया गया था, लेकिन उस उत्तर को बाद के लिए अलग-अलग तत्वों पर नज़र न रखने का नुकसान था:

for i in $sentence
do
  :
  # do whatever on $i
done

बैश ऐरे संदर्भ भी देखें ।


26
अफसोस की बात है, शेल-ग्लोबिंग के कारण बिल्कुल सही नहीं है: उम्मीद के बजाय touch NOPE; var='* a *'; arr=($var); set | grep ^arr=आउटपुटarr=([0]="NOPE" [1]="a" [2]="NOPE")arr=([0]="*" [1]="a" [2]="*")
टीनो

@ टिनो: यदि आप ग्लोबिंग को हस्तक्षेप नहीं करना चाहते हैं तो बस इसे बंद कर दें। समाधान तो वाइल्डकार्ड के साथ ही ठीक काम करेगा। यह मेरी राय में सबसे अच्छा तरीका है।
अलेक्जेंड्रोस

3
@ एलेक्सैंड्रो मेरा दृष्टिकोण केवल पैटर्न का उपयोग करना है, जो कि डिफ़ॉल्ट रूप से सुरक्षित हैं और हर संदर्भ में पूरी तरह से काम कर रहे हैं। एक सुरक्षित समाधान प्राप्त करने के लिए शेल-ग्लोबिंग को बदलने की आवश्यकता सिर्फ एक बहुत ही खतरनाक पथ से अधिक है, यह पहले से ही अंधेरे पक्ष है। इसलिए मेरी सलाह है कि कभी भी इस तरह के पैटर्न का उपयोग करने के आदी न बनें, क्योंकि जल्द या बाद में आप कुछ विस्तार के बारे में भूल जाएंगे, और फिर कोई आपके बग का शोषण करता है। आप प्रेस में इस तरह के कारनामों के लिए सबूत पा सकते हैं। हर एक। एक। डे।
तिनो

86

बस शेल का उपयोग करें "सेट" अंतर्निहित। उदाहरण के लिए,

$ पाठ सेट करें

उसके बाद, $ पाठ में अलग-अलग शब्द $ 1, $ 2, $ 3, आदि में होंगे। मजबूती के लिए, आमतौर पर एक करता है

सेट - जंक $ टेक्स्ट
खिसक जाना

मामले को संभालने के लिए जहां $ पाठ खाली है या डैश के साथ शुरू होता है। उदाहरण के लिए:

पाठ = "यह एक परीक्षा है"
सेट - जंक $ टेक्स्ट
खिसक जाना
शब्द के लिए; करना
  इको "[$ शब्द]"
किया हुआ

यह प्रिंट करता है

[यह]
[है]
[ए]
[परीक्षा]

5
यह var को विभाजित करने का एक शानदार तरीका है ताकि अलग-अलग हिस्सों को सीधे एक्सेस किया जा सके। +1; मेरी समस्या हल कर दी
Cheekysoft

मैं उपयोग करने का सुझाव देने जा रहा था awkलेकिन setबहुत आसान है। मैं अब एक setप्रशंसक हूं । धन्यवाद @Idelic!
यजीर रामिरेज़

22
कृपया शेल ग्लोबिंग से अवगत रहें यदि आप ऐसी चीजें करते हैं: अपेक्षित के बजाय touch NOPE; var='* a *'; set -- $var; for a; do echo "[$a]"; doneआउटपुट । केवल 101% सुनिश्चित करने के लिए इसका उपयोग करें कि स्प्लिटेड स्ट्रिंग में कोई शेल मेटाचैकर्स नहीं हैं! [NOPE] [a] [NOPE][*] [a] [*]
टीनो

4
@ टिनो: यह मुद्दा हर जगह लागू होता है, न केवल यहां, बल्कि इस मामले में आप ग्लोबिंग को अक्षम करने के लिए set -fपहले set -- $varऔर set +fबाद में कर सकते हैं।
इडेलिक

3
@Idelic: अच्छी पकड़। set -fअपने समाधान के साथ सुरक्षित भी है। लेकिन set +fप्रत्येक शेल का डिफ़ॉल्ट है, इसलिए यह एक आवश्यक विवरण है, जिस पर ध्यान दिया जाना चाहिए, क्योंकि दूसरों को शायद इसके बारे में पता नहीं है (जैसा कि मैं भी था)।
टीनो

81

BASH 3 और इसके बाद के संस्करण में शायद सबसे आसान और सबसे सुरक्षित तरीका है:

var="string    to  split"
read -ra arr <<<"$var"

( arrवह सरणी कहां है जो स्ट्रिंग के विभाजित भागों को लेती है) या, अगर इनपुट में नईलाइन हो सकती हैं और आप केवल पहली पंक्ति से अधिक चाहते हैं:

var="string    to  split"
read -ra arr -d '' <<<"$var"

(कृपया इसमें स्थान नोट करें -d '', इसे छोड़ा नहीं जा सकता है), लेकिन इससे आपको एक अनपेक्षित न्यूलाइन मिल सकती है <<<"$var"(जैसा कि यह अंत में एक LF जोड़ता है)।

उदाहरण:

touch NOPE
var="* a  *"
read -ra arr <<<"$var"
for a in "${arr[@]}"; do echo "[$a]"; done

अपेक्षित उत्पादन करता है

[*]
[a]
[*]

इस समाधान के रूप में (यहां पिछले सभी समाधानों के विपरीत) अप्रत्याशित और अक्सर बेकाबू खोल गोलाबारी होने का खतरा नहीं है।

इसके अलावा यह आपको IFS की पूरी शक्ति देता है जैसा कि आप शायद चाहते हैं:

उदाहरण:

IFS=: read -ra arr < <(grep "^$USER:" /etc/passwd)
for a in "${arr[@]}"; do echo "[$a]"; done

आउटपुट कुछ इस तरह है:

[tino]
[x]
[1000]
[1000]
[Valentin Hilbig]
[/home/tino]
[/bin/bash]

जैसा कि आप देख सकते हैं, रिक्त स्थान को इस तरह भी संरक्षित किया जा सकता है:

IFS=: read -ra arr <<<' split  :   this    '
for a in "${arr[@]}"; do echo "[$a]"; done

आउटपुट

[ split  ]
[   this    ]

कृपया ध्यान दें कि IFSBASH में हैंडलिंग अपने आप में एक विषय है, इसलिए अपने परीक्षण, इस पर कुछ दिलचस्प विषय करें:

  • unset IFS: SPC, TAB, NL के रन और इग्नोर शुरू होते हैं और समाप्त होते हैं
  • IFS='': कोई भी क्षेत्र अलग नहीं है, बस सब कुछ पढ़ता है
  • IFS=' ': एसपीसी के रन (और एसपीसी केवल)

कुछ अंतिम उदाहरण

var=$'\n\nthis is\n\n\na test\n\n'
IFS=$'\n' read -ra arr -d '' <<<"$var"
i=0; for a in "${arr[@]}"; do let i++; echo "$i [$a]"; done

आउटपुट

1 [this is]
2 [a test]

जबकि

unset IFS
var=$'\n\nthis is\n\n\na test\n\n'
read -ra arr -d '' <<<"$var"
i=0; for a in "${arr[@]}"; do let i++; echo "$i [$a]"; done

आउटपुट

1 [this]
2 [is]
3 [a]
4 [test]

Btw:

  • यदि आपको इसकी आदत नहीं $'ANSI-ESCAPED-STRING'है, तो यह एक समय है।

  • यदि आप -r(जैसे read -a arr <<<"$var") को शामिल नहीं करते हैं, तो रीड बैकलैश बच जाता है। इसे पाठक के लिए व्यायाम के रूप में छोड़ दिया जाता है।


दूसरे प्रश्न के लिए:

एक स्ट्रिंग में किसी चीज के लिए परीक्षण करने के लिए मैं आमतौर पर चिपक जाता हूं case, क्योंकि यह एक ही बार में कई मामलों की जांच कर सकता है (ध्यान दें: केस केवल पहले मैच को निष्पादित करता है, यदि आपको फैलिपल caseस्टेटमेंट का उपयोग करने की आवश्यकता है), और यह आवश्यकता अक्सर होती है (वाक्य इरादा):

case "$var" in
'')                empty_var;;                # variable is empty
*' '*)             have_space "$var";;        # have SPC
*[[:space:]]*)     have_whitespace "$var";;   # have whitespaces like TAB
*[^-+.,A-Za-z0-9]*) have_nonalnum "$var";;    # non-alphanum-chars found
*[-+.,]*)          have_punctuation "$var";;  # some punctuation chars found
*)                 default_case "$var";;      # if all above does not match
esac

तो आप एसपीसी की जाँच के लिए रिटर्न मान को इस तरह सेट कर सकते हैं:

case "$var" in (*' '*) true;; (*) false;; esac

क्यों case? क्योंकि यह आमतौर पर रेगेक्स अनुक्रमों की तुलना में थोड़ा अधिक पठनीय है, और शेल मेटाचैकर्स के लिए धन्यवाद यह 99% सभी आवश्यकताओं को अच्छी तरह से संभालता है।


2
यह जवाब अधिक उभार के योग्य है, क्योंकि झुलसने वाले मुद्दों पर प्रकाश डाला गया है, और इसकी व्यापकता
ब्रायन एग्न्यू

@ ब्रायन धन्यवाद। कृपया ध्यान दें कि आप ग्लोबिंग का उपयोग set -fया set -o noglobस्विच कर सकते हैं , जैसे कि शेल मेटाचैकर इस संदर्भ में कोई नुकसान नहीं पहुंचाते हैं। लेकिन मैं वास्तव में इसका मित्र नहीं हूं, क्योंकि यह शेल की बहुत अधिक शक्ति को पीछे छोड़ देता है / इस सेटिंग को आगे और पीछे स्विच करने में बहुत त्रुटि है।
तिनो

2
अद्भुत जवाब, वास्तव में अधिक उत्थान के हकदार हैं। केस के पतन के माध्यम से साइड नोट - आप इसे ;&प्राप्त कर सकते हैं । निश्चित नहीं है कि किस संस्करण में बैश दिखाई दिया। मैं एक 4.3 उपयोगकर्ता हूँ
Sergiy Kolodyazhnyy

2
@ ध्यान देने योग्य धन्यवाद, क्योंकि मुझे यह अभी तक पता नहीं था! इसलिए मैंने इसे देखा, यह बैश 4 में दिखाई दिया । ;&सी। की तरह पैटर्न जांच के लिए मजबूर किया गया है और वहाँ भी है ;;&जो आगे पैटर्न की जाँच करने के लिए जारी है। तो ;;जैसा है if ..; then ..; else if ..और ;;&जैसा है if ..; then ..; fi; if .., जहां ;&है, जैसे है m=false; if ..; then ..; m=:; fi; if $m || ..; then ..- कोई भी कभी (दूसरों से) सीखना बंद नहीं करता;)
टीनो

@ यह बिल्कुल सच है - सीखना एक सतत प्रक्रिया है। वास्तव में, ;;&आपको टिप्पणी करने से पहले मुझे पता नहीं था : D धन्यवाद, और शेल आपके साथ हो सकता है;)
Sergiy Kolodyazhnyy

43
$ echo "This is   a sentence." | tr -s " " "\012"
This
is
a
sentence.

रिक्त स्थान की जाँच के लिए, grep का उपयोग करें:

$ echo "This is   a sentence." | grep " " > /dev/null
$ echo $?
0
$ echo "Thisisasentence." | grep " " > /dev/null     
$ echo $?
1

1
BASH में echo "X" |आमतौर पर <<<"X"इस तरह से प्रतिस्थापित किया जा सकता है grep -s " " <<<"This contains SPC":। यदि आप echo X | read varइसके विपरीत कुछ करते हैं तो आप अंतर को देख सकते हैं read var <<< Xvarवर्तमान शेल में केवल बाद का आयात चर है , जबकि पहले संस्करण में इसे एक्सेस करने के लिए आपको इस तरह का समूह बनाना होगा:echo X | { read var; handle "$var"; }
टीनो

17

(ए) एक वाक्य को उसके शब्दों में विभाजित करने के लिए (अंतरिक्ष अलग) आप बस का उपयोग करके डिफ़ॉल्ट IFS का उपयोग कर सकते हैं

array=( $string )


उदाहरण निम्नलिखित स्निपेट को चलाने वाला

#!/bin/bash

sentence="this is the \"sentence\"   'you' want to split"
words=( $sentence )

len="${#words[@]}"
echo "words counted: $len"

printf "%s\n" "${words[@]}" ## print array

उत्पादन होगा

words counted: 8
this
is
the
"sentence"
'you'
want
to
split

जैसा कि आप देख सकते हैं कि आप बिना किसी समस्या के भी सिंगल या डबल कोट्स का उपयोग कर सकते हैं

नोट्स:
- यह मूल रूप से भीड़ के उत्तर के समान है, लेकिन इस तरह से आप किसी भी आगे की जरूरत के लिए सरणी स्टोर करते हैं। यदि आपको केवल एक लूप की आवश्यकता है, तो आप उसके उत्तर का उपयोग कर सकते हैं, जो कि एक पंक्ति छोटी है :)
- कृपया इस प्रश्न को वैकल्पिक तरीकों के लिए सीमांकक के आधार पर एक स्ट्रिंग को विभाजित करने के लिए देखें ।


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

regex='\s{1,}'
if [[ "$sentence" =~ $regex ]]
    then
        echo "Space here!";
fi

रेगेक्स संकेत (बी) ए 1 के लिए, लेकिन गलत समाधान के लिए -1 (ए) के रूप में यह शेल गोलाबारी के लिए त्रुटि प्रवण है। ;)
टिनो


1
echo $WORDS | xargs -n1 echo

यह प्रत्येक शब्द को आउटपुट करता है, आप उस सूची को प्रोसेस कर सकते हैं जैसा कि आप बाद में फिट देखते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.