bash - मैं बैश में एक डिलीमीटर पर एक स्ट्रिंग कैसे विभाजित करूं?




shell scripting (20)

संगत उत्तर

इस सवाल के लिए, bash में ऐसा करने के लिए पहले से ही बहुत अलग तरीका है। लेकिन बाश में कई विशेष विशेषताएं हैं, जिन्हें bashism कहा जाता है जो अच्छी तरह से काम करते हैं, लेकिन यह किसी भी अन्य shell में काम नहीं करेगा।

विशेष रूप से, सरणी , सहयोगी सरणी , और पैटर्न प्रतिस्थापन शुद्ध bashisms हैं और अन्य गोले के तहत काम नहीं कर सकते हैं

मेरे डेबियन जीएनयू / लिनक्स पर , dash नामक एक मानक खोल है, लेकिन मुझे कई लोग जानते हैं जो ksh का उपयोग करना पसंद करते हैं।

अंत में, बहुत छोटी स्थिति में, एक विशेष उपकरण है जिसे व्यस्त busybox कहा जाता है जिसमें अपने स्वयं के खोल दुभाषिया ( ash ) होते हैं।

अनुरोधित स्ट्रिंग

एसओ सवाल में स्ट्रिंग नमूना है:

IN="[email protected];[email protected]"

चूंकि यह सफेद जगहों के साथ उपयोगी हो सकता है और चूंकि व्हाइटस्पेस दिनचर्या के परिणाम को संशोधित कर सकता है, इसलिए मैं इस नमूना स्ट्रिंग का उपयोग करना पसंद करता हूं:

 IN="[email protected];[email protected];Full Name <[email protected]>"

bash में डिलीमीटर पर आधारित स्प्लिट स्ट्रिंग (संस्करण> = 4.2)

शुद्ध बैश के तहत, हम सरणी और आईएफएस का उपयोग कर सकते हैं:

var="[email protected];[email protected];Full Name <[email protected]>"

oIFS="$IFS"
IFS=";"
declare -a fields=($var)
IFS="$oIFS"
unset oIFS

IFS=\; read -a fields <<<"$var"

हालिया बैश के तहत इस वाक्यविन्यास का उपयोग वर्तमान सत्र के लिए $IFS नहीं बदलता है, लेकिन केवल वर्तमान कमांड के लिए:

set | grep ^IFS=
IFS=$' \t\n'

अब स्ट्रिंग var विभाजित है और एक सरणी (नाम fields ) में संग्रहीत है:

set | grep ^fields=\\\|^var=
fields=([0]="[email protected]" [1]="[email protected]" [2]="Full Name <[email protected]>")
var='[email protected];[email protected];Full Name <[email protected]>'

हम declare -p साथ परिवर्तनीय सामग्री के लिए अनुरोध कर सकते हैं declare -p :

declare -p var fields
declare -- var="[email protected];[email protected];Full Name <[email protected]>"
declare -a fields=([0]="[email protected]" [1]="[email protected]" [2]="Full Name <[email protected]>")

read विभाजन करने का सबसे तेज़ तरीका है, क्योंकि कोई कांटा नहीं है और कोई बाहरी संसाधन नहीं कहा जाता है।

वहां से, आप प्रत्येक फ़ील्ड को प्रोसेस करने के लिए पहले से ही सिंटैक्स का उपयोग कर सकते हैं:

for x in "${fields[@]}";do
    echo "> [$x]"
    done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

या प्रसंस्करण के बाद प्रत्येक फ़ील्ड को छोड़ दें (मुझे यह स्थानांतरण दृष्टिकोण पसंद है):

while [ "$fields" ] ;do
    echo "> [$fields]"
    fields=("${fields[@]:1}")
    done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

या यहां तक ​​कि सरल प्रिंटआउट (छोटे वाक्यविन्यास) के लिए भी:

printf "> [%s]\n" "${fields[@]}"
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

shell में डेलीमीटर पर आधारित स्प्लिट स्ट्रिंग

लेकिन अगर आप कई गोले के नीचे कुछ उपयोग करने योग्य लिखेंगे, तो आपको बैशिस का उपयोग नहीं करना होगा

एक सिंटैक्स होता है, जो कई गोले में उपयोग किया जाता है, एक स्ट्रिंग को पहली या आखिरी घटना में स्ट्रिंग को विभाजित करने के लिए:

${var#*SubStr}  # will drop begin of string up to first occur of `SubStr`
${var##*SubStr} # will drop begin of string up to last occur of `SubStr`
${var%SubStr*}  # will drop part of string from last occur of `SubStr` to the end
${var%%SubStr*} # will drop part of string from first occur of `SubStr` to the end

(इसका लापता मेरे उत्तर प्रकाशन का मुख्य कारण है;)

जैसा कि Score_Under द्वारा इंगित किया गया है:

# और % सबसे कम संभव मिलान स्ट्रिंग को हटा दें, और

## और %% सबसे लंबा संभव हटाएं।

यह छोटी नमूना स्क्रिप्ट bash , dash , ksh , ksh तहत अच्छी तरह से काम करती है और मैक-ओएस के बैश के तहत भी परीक्षण किया गया था:

var="[email protected];[email protected];Full Name <[email protected]>"
while [ "$var" ] ;do
    iter=${var%%;*}
    echo "> [$iter]"
    [ "$var" = "$iter" ] && \
        var='' || \
        var="${var#*;}"
  done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

मज़े करो!

मेरे पास यह चर एक चर में संग्रहीत है:

IN="[email protected];[email protected]"

अब मैं तारों को विभाजित करना चाहता हूं ; delimiter ताकि मेरे पास है:

ADDR1="[email protected]"
ADDR2="[email protected]"

मुझे ADDR1 और ADDR2 चर की आवश्यकता नहीं है। अगर वे एक सरणी के तत्व हैं जो बेहतर भी हैं।

नीचे दिए गए उत्तरों से सुझावों के बाद, मैं निम्नलिखित के साथ समाप्त हुआ जो मैं बाद में था:

#!/usr/bin/env bash

IN="[email protected];[email protected]"

mails=$(echo $IN | tr ";" "\n")

for addr in $mails
do
    echo "> [$addr]"
done

आउटपुट:

> [[email protected]]
> [[email protected]]

Internal_field_separator (IFS) को सेट करने में एक समाधान था ; । मुझे यकीन नहीं है कि उस उत्तर के साथ क्या हुआ, आप डिफ़ॉल्ट रूप से IFS को रीसेट कैसे करते हैं?

आरई: IFS समाधान, मैंने कोशिश की और यह काम करता है, मैं पुराने IFS रखता हूं और फिर इसे बहाल करता हूं:

IN="[email protected];[email protected]"

OIFS=$IFS
IFS=';'
mails2=$IN
for x in $mails2
do
    echo "> [$x]"
done

IFS=$OIFS

बीटीडब्ल्यू, जब मैंने कोशिश की

mails2=($IN)

लूप में इसे प्रिंट करते समय मुझे केवल पहली स्ट्रिंग मिल गई, बिना $IN ब्रैकेट के काम करता है।


';' से अलग एक स्ट्रिंग को विभाजित करने के लिए एक-लाइनर एक सरणी में है:

IN="[email protected];[email protected]"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}

यह केवल एक सबहेल में आईएफएस सेट करता है, इसलिए आपको इसके मूल्य को सहेजने और पुनर्स्थापित करने की चिंता करने की आवश्यकता नहीं है।


आप Internal_field_separator (आईएफएस) वैरिएबल सेट कर सकते हैं, और उसके बाद इसे सरणी में पार्स कर दें। जब यह कमांड में होता है, तो IFS को असाइनमेंट केवल उस एकल कमांड के पर्यावरण ( read ) में होता है। इसके बाद IFS वैरिएबल वैल्यू के अनुसार इनपुट को एक सरणी में पार्स किया जाता है, जिसे हम फिर से चालू कर सकते हैं।

IFS=';' read -ra ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
    # process "$i"
done

यह अलग-अलग वस्तुओं की एक पंक्ति को पार्स करेगा ; , इसे एक सरणी में धक्का दे रहा है। पूरे $IN को प्रोसेस करने के लिए सामग्री, प्रत्येक बार इनपुट की एक पंक्ति अलग हो जाती है ; :

 while IFS=';' read -ra ADDR; do
      for i in "${ADDR[@]}"; do
          # process "$i"
      done
 done <<< "$IN"

इस तरह एक सरल और स्मार्ट तरीका है:

echo "add:sfff" | xargs -d: -i  echo {}

लेकिन आपको gnu xargs, बीएसडी xargs cant समर्थन -d delim का उपयोग करना होगा। यदि आप मेरे जैसे सेब मैक का उपयोग करते हैं। आप gnu xargs इंस्टॉल कर सकते हैं:

brew install findutils

फिर

echo "add:sfff" | gxargs -d: -i  echo {}

एंड्रॉइड खोल में, प्रस्तावित तरीकों में से ज्यादातर काम नहीं करते हैं:

$ IFS=':' read -ra ADDR <<<"$PATH"                             
/system/bin/sh: can't create temporary file /sqlite_stmt_journals/mksh.EbNoR10629: No such file or directory

काम क्या है:

$ for i in ${PATH//:/ }; do echo $i; done
/sbin
/vendor/bin
/system/sbin
/system/bin
/system/xbin

जहां // वैश्विक प्रतिस्थापन का मतलब है।


दो बोर्न-आश विकल्प जहां न तो बैश सरणी की आवश्यकता होती है:

प्रकरण 1 : इसे अच्छा और सरल रखें: रिकॉर्ड-सेपरेटर के रूप में एक न्यूलाइन का उपयोग करें ... उदाहरण के लिए।

IN="[email protected]
[email protected]"

while read i; do
  # process "$i" ... eg.
    echo "[email:$i]"
done <<< "$IN"

नोट: इस पहले मामले में सूची में हेरफेर के साथ सहायता करने के लिए कोई सब-प्रोसेस नहीं है।

आइडिया: शायद यह आंतरिक रूप से एनएल का उपयोग करने के लायक है, और केवल अंतिम परिणाम उत्पन्न करते समय केवल एक अलग आरएस में परिवर्तित हो रहा है।

प्रकरण 2 : ";" का उपयोग करना एक रिकॉर्ड विभाजक के रूप में ... उदाहरण के लिए।

NL="
" IRS=";" ORS=";"

conv_IRS() {
  exec tr "$1" "$NL"
}

conv_ORS() {
  exec tr "$NL" "$1"
}

IN="[email protected];[email protected]"
IN="$(conv_IRS ";" <<< "$IN")"

while read i; do
  # process "$i" ... eg.
    echo -n "[email:$i]$ORS"
done <<< "$IN"

लूप के पूरा होने के बाद दोनों मामलों में लूप के भीतर एक उप-सूची बनाई जा सकती है। यह तब उपयोगी होता है जब स्मृति में सूचियों को जोड़ना, बजाय फाइलों में सूचियां संग्रहीत करना। {ps शांत रहें और बी-) पर जाएं


पहले से प्रदान किए गए शानदार उत्तरों के अलावा, यदि यह डेटा को प्रिंट करने का मामला है तो आप awk का उपयोग करने पर विचार कर सकते हैं:

awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"

यह क्षेत्र विभाजक को सेट करता है ; , ताकि यह लूप के साथ फ़ील्ड के माध्यम से लूप कर सके और तदनुसार प्रिंट करें।

परीक्षा

$ IN="[email protected];[email protected]"
$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
> [[email protected]]
> [[email protected]]

एक और इनपुट के साथ:

$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "a;b;c   d;e_;f"
> [a]
> [b]
> [c   d]
> [e_]
> [f]

बैश में, एक बुलेट प्रूफ तरीका, यह तब भी काम करेगा जब आपके चर में न्यूलाइन हो:

IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")

देखो:

$ in=$'one;two three;*;there is\na newline\nin this field'
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two three" [2]="*" [3]="there is
a newline
in this field")'

काम करने के लिए यह चाल एक खाली डेलीमीटर के साथ read (डेलिमिटर) के -d विकल्प का उपयोग करना है, ताकि read जाता है जो इसे खिलाया गया सब कुछ read लिए मजबूर हो जाता है। और हम printf के लिए कोई पीछे की नईलाइन धन्यवाद के साथ, चर के बिल्कुल सामग्री के साथ read फ़ीड। ध्यान दें कि हम यह सुनिश्चित करने के लिए डिलीमीटर को printf में भी डाल रहे हैं कि read लिए पारित स्ट्रिंग में पिछला डिलीमीटर है। इसके बिना, read संभावित क्षेत्रों को पीछे हटाना होगा:

$ in='one;two;three;'    # there's an empty field
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two" [2]="three" [3]="")'

पिछला खाली क्षेत्र संरक्षित है।

Bash≥4.4 के लिए अद्यतन करें

बैश 4.4 के बाद से, mapfile (उर्फ readarray ) एक डिलीमीटर निर्दिष्ट करने के लिए -d विकल्प का समर्थन करता है। इसलिए एक और कैनोलिक तरीका है:

mapfile -d ';' -t array < <(printf '%s;' "$in")

मैंने cut कमांड का संदर्भ देने के कुछ जवाब देखे हैं, लेकिन वे सभी हटा दिए गए हैं। यह थोड़ा अजीब बात है कि किसी ने उस पर विस्तार नहीं किया है, क्योंकि मुझे लगता है कि यह इस तरह की चीज करने के लिए अधिक उपयोगी आदेशों में से एक है, खासकर सीमांकित लॉग फाइलों को पार्स करने के लिए।

इस विशिष्ट उदाहरण को एक बैश स्क्रिप्ट सरणी में विभाजित करने के मामले में, tr शायद अधिक कुशल है, लेकिन cut का उपयोग किया जा सकता है, और यदि आप विशिष्ट फ़ील्ड को मध्य से खींचना चाहते हैं तो अधिक प्रभावी है।

उदाहरण:

$ echo "[email protected];[email protected]" | cut -d ";" -f 1
[email protected]
$ echo "[email protected];[email protected]" | cut -d ";" -f 2
[email protected]

आप स्पष्ट रूप से इसे एक लूप में डाल सकते हैं, और प्रत्येक फ़ील्ड को स्वतंत्र रूप से खींचने के लिए -f पैरामीटर को फिर से चालू कर सकते हैं।

जब आपके पास पंक्तियों के साथ एक सीमित लॉग फ़ाइल है तो यह अधिक उपयोगी हो जाता है:

2015-04-27|12345|some action|an attribute|meta data

cut इस फ़ाइल को cat करने में सक्षम होने के लिए बहुत आसान है और आगे की प्रक्रिया के लिए एक विशेष क्षेत्र का चयन करें।


यदि आप उन्हें तुरंत संसाधित नहीं करते हैं, तो मुझे यह करना अच्छा लगता है:

for i in $(echo $IN | tr ";" "\n")
do
  # process
done

आप सरणी को प्रारंभ करने के लिए इस तरह के लूप का उपयोग कर सकते हैं, लेकिन ऐसा करने का शायद एक आसान तरीका है। उम्मीद है कि यह मदद करता है, यद्यपि।


यदि कोई जगह नहीं है, तो यह क्यों नहीं?

IN="[email protected];[email protected]"
arr=(`echo $IN | tr ';' ' '`)

echo ${arr[0]}
echo ${arr[1]}

यह करने का यह सबसे आसान तरीका है।

spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}

यह मेरे लिए काम किया:

string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2

यहां एक साफ 3-लाइनर है:

in="[email protected];[email protected];[email protected];[email protected]"
IFS=';' list=($in)
for item in "${list[@]}"; do echo $item; done

जहां विभाजक पर आधारित IFS सीमित शब्द और () का उपयोग array बनाने के लिए किया जाता है। फिर [@] का उपयोग प्रत्येक आइटम को एक अलग शब्द के रूप में वापस करने के लिए किया जाता है।

यदि उसके बाद आपके पास कोई कोड है, तो आपको $IFS को पुनर्स्थापित करने की भी आवश्यकता है, उदाहरण के लिए unset IFS



[email protected] सरणी को लोड करने के लिए अंतर्निहित set उपयोग करें:

IN="[email protected];[email protected]"
IFS=';'; set $IN; IFS=$' \t\n'

फिर, पार्टी शुरू करें:

echo $#
for a; do echo $a; done
ADDR1=$1 ADDR2=$2

Maybe not the most elegant solution, but works with * and spaces:

IN="[email protected] me.com;*;[email protected]"
for i in `delims=${IN//[^;]}; seq 1 $((${#delims} + 1))`
do
   echo "> [`echo $IN | cut -d';' -f$i`]"
done

Outputs

> [[email protected] me.com]
> [*]
> [[email protected]]

Other example (delimiters at beginning and end):

IN=";[email protected] me.com;*;[email protected];"
> []
> [[email protected] me.com]
> [*]
> [[email protected]]
> []

Basically it removes every character other than ; making delims eg. ;;; । Then it does for loop from 1 to number-of-delimiters as counted by ${#delims} . The final step is to safely get the $i th part using cut .


Okay guys!

Here's my answer!

DELIMITER_VAL='='

read -d '' F_ABOUT_DISTRO_R <<"EOF"
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"
NAME="Ubuntu"
VERSION="14.04.4 LTS, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04.4 LTS"
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
EOF

SPLIT_NOW=$(awk -F$DELIMITER_VAL '{for(i=1;i<=NF;i++){printf "%s\n", $i}}' <<<"${F_ABOUT_DISTRO_R}")
while read -r line; do
   SPLIT+=("$line")
done <<< "$SPLIT_NOW"
for i in "${SPLIT[@]}"; do
    echo "$i"
done

Why this approach is "the best" for me?

Because of two reasons:

  1. You do not need to escape the delimiter;
  2. You will not have problem with blank spaces . The value will be properly separated in the array!

[]'s



IN='[email protected];[email protected];Charlie Brown <[email protected];!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'
set -f
oldifs="$IFS"
IFS=';'; arrayIN=($IN)
IFS="$oldifs"
for i in "${arrayIN[@]}"; do
echo "$i"
done
set +f

आउटपुट:

[email protected]
[email protected]
Charlie Brown <[email protected]
!"#$%&/()[]{}*? are no problem
simple is beautiful :-)

स्पष्टीकरण: कोष्ठक का उपयोग करके सरल असाइनमेंट () अर्धविराम से अलग सूची को एक सरणी में परिवर्तित करता है बशर्ते आपके पास सही आईएफएस हो। लूप के लिए मानक सामान्य रूप से उस सरणी में अलग-अलग आइटम को संभालता है। ध्यान दें कि आईएन वैरिएबल के लिए दी गई सूची को "हार्ड" उद्धृत किया जाना चाहिए, यानी एकल टिक के साथ।

आईएफएस को सहेजा और बहाल किया जाना चाहिए क्योंकि बैश कमांड के समान तरीके से असाइनमेंट का इलाज नहीं करता है। एक वैकल्पिक वर्कअराउंड एक फ़ंक्शन के अंदर असाइनमेंट को लपेटना है और संशोधित आईएफएस के साथ उस फ़ंक्शन को कॉल करना है। उस मामले में आईएफएस की अलग बचत / बहाली की आवश्यकता नहीं है। इसे इंगित करने के लिए "Bize" के लिए धन्यवाद।





scripting