क्या आईएफएस (इंटरनल फील्ड सेपरेटर) एक ही विभाजक के रूप में कई लगातार सीमांकक वर्णों के लिए कार्य कर सकता है?


10

गैर-श्वेत अंतरिक्ष मूल्यों के साथ IFS का उपयोग करके एक सरणी को पार्स करना रिक्त तत्व बनाता है।
यहां तक ​​कि tr -sएक ही परिसीमन में कई डिले को सिकोड़ने के लिए पर्याप्त नहीं है।
एक उदाहरण अधिक स्पष्ट रूप से इस मुद्दे की व्याख्या कर सकता है ..
क्या IFS के एक tweaking के माध्यम से "सामान्य" परिणाम प्राप्त करने का एक तरीका है (क्या IFS के व्यवहार को बदलने के लिए एक संबद्ध सेटिंग है? .... अर्थात। डिफ़ॉल्ट व्हाट्सएप के समान कार्य करने के लिए। भारतीय विदेश सेवा।

var=" abc  def   ghi    "
echo "============== IFS=<default>"
arr=($var)
for x in ${!arr[*]} ; do
   echo "# arr[$x] \"${arr[x]}\""
done
#
sfi="$IFS" ; IFS=':'
set -f # Disable file name generation (globbing)
       # (This  data won't "glob", but unless globbing     
       #  is actually needed, turn if off, because   
       #  unusual/unexpected combinations of data can glob!
       #  and they can do it in the most obscure ways...  
       #  With IFS, "you're not in Kansas any more! :)  
var=":abc::def:::ghi::::"
echo "============== IFS=$IFS"
arr=($var)
for x in ${!arr[*]} ; do
   echo "# arr[$x] \"${arr[x]}\""
done
echo "============== IFS=$IFS and tr"
arr=($(echo -n "$var"|tr -s "$IFS"))
for x in ${!arr[*]} ; do
   echo "# arr[$x] \"${arr[x]}\""
done
set +f     # enable globbing 
IFS="$sfi" # re-instate original IFS val
echo "============== IFS=<default>"

यहाँ आउटपुट है


============== IFS=<default>
# arr[0] "abc"
# arr[1] "def"
# arr[2] "ghi"
============== IFS=:
# arr[0] ""
# arr[1] "abc"
# arr[2] ""
# arr[3] "def"
# arr[4] ""
# arr[5] ""
# arr[6] "ghi"
# arr[7] ""
# arr[8] ""
# arr[9] ""
============== IFS=: and tr
# arr[0] ""
# arr[1] "abc"
# arr[2] "def"
# arr[3] "ghi"
============== IFS=<default>

एक ही सवाल का एक बेहतर (मुझे लगता है) जवाब है : stackoverflow.com/a/14789518/1765658
एफ। होरी

जवाबों:


3

एकाधिक (गैर-स्थान) लगातार परिसीमाक चार्ट को हटाने के लिए, दो (स्ट्रिंग / सरणी) पैरामीटर विस्तार का उपयोग किया जा सकता है। IFSसरणी पैरामीटर विस्तार के लिए चर को खाली स्ट्रिंग पर सेट करना है ।

यह शब्द विभाजन केman bash अंतर्गत प्रलेखित है :

बिना किसी निहित मान के विस्तार के परिणामस्वरूप अनियंत्रित निहितार्थ तर्क, हटा दिए जाते हैं।

(
set -f
str=':abc::def:::ghi::::'
IFS=':'
arr=(${str})
IFS=""
arr=(${arr[@]})

echo ${!arr[*]}

for ((i=0; i < ${#arr[@]}; i++)); do 
   echo "${i}: '${arr[${i}]}'"
done
)

अच्छा! एक सरल और प्रभावी तरीका - एक बैश लूप की आवश्यकता नहीं है और एक उपयोगिता ऐप को कॉल करने की आवश्यकता नहीं है - बीटीडब्ल्यू। जैसा कि आपने "(गैर-स्थान)" का उल्लेख किया है , मैं स्पष्टता के लिए कहना चाहूंगा कि यह अंतरिक्ष सहित सीमांकक वर्णों के किसी भी संयोजन के साथ ठीक काम करता है।
पीटर।

मेरे परीक्षणों में सेटिंग IFS=' '(अर्थात एक व्हाट्सएप) समान व्यवहार करता है। मुझे यह स्पष्ट अशक्त तर्क ("" या '') की तुलना में कम भ्रामक लगता है IFS
मीका विडेनमैन

यदि आपके डेटा में एम्बेडेड व्हाट्सएप है तो यह एक भयानक समाधान है। यदि आपका डेटा 'abc' के बजाय 'bc' था, तो IFS = "" 'a' को 'bc' से अलग तत्व में विभाजित करेगा।
देजय क्लेटन

5

से bashमैनपेज:

IFS में कोई भी वर्ण जो IFS व्हाट्सएप नहीं है, साथ ही किसी भी निकटवर्ती IFS व्हाट्सएप वर्ण के साथ, एक क्षेत्र का परिसीमन करता है। IFS व्हॉट्सएप पात्रों का एक क्रम भी एक सीमांकक के रूप में माना जाता है।

इसका मतलब है कि IFS व्हाट्सएप (स्पेस, टैब और न्यूलाइन) को अन्य विभाजकों की तरह व्यवहार नहीं किया जाता है। आप एक वैकल्पिक विभाजक के साथ बिल्कुल वैसा ही व्यवहार करना चाहते हैं, तो आप कुछ विभाजक की मदद से अदला-बदली कर सकते हैं trया sed:

var=":abc::def:::ghi::::"
arr=($(echo -n $var | sed 's/ /%#%#%#%#%/g;s/:/ /g'))
for x in ${!arr[*]} ; do
   el=$(echo -n $arr | sed 's/%#%#%#%#%/ /g')
   echo "# arr[$x] \"$el\""
done

%#%#%#%#%बात क्षेत्रों के अंदर संभव रिक्त स्थान को बदलने के लिए एक जादू मूल्य है, यह "अद्वितीय" (या बहुत unlinkely) होने की उम्मीद है। यदि आप सुनिश्चित हैं कि कोई भी स्थान कभी भी खेतों में नहीं होगा, तो बस इस हिस्से को छोड़ दें)।


@ फ़र्ज़ीएस ... धन्यवाद (मेरे प्रश्न में मॉडरेटटन देखें) ... आपने मुझे मेरे इच्छित प्रश्न का उत्तर दिया हो सकता है .. और वह उत्तर शायद (शायद यह है) "IFS को व्यवहार में लाने का कोई तरीका नहीं है" ढंग से मैं चाहता हूं "... मैं trसमस्या दिखाने के लिए उदाहरणों को बताता हूं ... मैं एक सिस्टम कॉल से बचना चाहता हूं, इसलिए मैं एक बैश विकल्प पर नजर ${var##:}डालूंगा , जिसके आगे मैंने अपनी टिप्पणी में ग्लेन के एंसर का उल्लेख किया है ...।
.मैं

यह उपचार IFSसभी बॉर्न-शैली के गोले में समान है, यह पोसिक्स में निर्दिष्ट है
गिलेस एसओ- बुराई को रोकना '

4-प्लस साल के बाद से मैंने यह सवाल पूछा - मैंने पाया कि @ नाज़ाद का जवाब (एक साल पहले पोस्ट किया गया) IFS को हथकंडा करने के लिए सबसे सरल तरीका है कि किसी भी संख्या के साथ एक सरणी बनाने के लिए और IFSसीमांकक-स्ट्रिंग के रूप में संयोजन । मेरे सवाल का सबसे अच्छा जवाब दिया गया था jon_d, लेकिन @ नज़ाद का जवाब IFSबिना लूप और बिना यूटिलिटी ऐप के इस्तेमाल करने का एक नितांत तरीका है ।
पीटर

2

जैसा कि बैश IFS एक सीमांकक के रूप में लगातार परिसीमन वर्णों (गैर-व्हाट्सएप सीमांकक) के रूप में इलाज करने के लिए एक घर में रास्ता नहीं प्रदान करता है, मैंने एक सभी बैश संस्करण (बाहरी कॉल जैसे, ट्रे, awk, sed) को एक साथ रखा है। )

यह बहु-चार IFS को संभाल सकता है।

यहाँ इसके निष्पादन-समय के लिए resu; ts, के लिए समान परीक्षणों के साथ हैं trawkइस Q / A पृष्ठ पर दिखाए गए विकल्पों और विकल्पों के ... ये परीक्षण केवल एरे के निर्माण के बिना (I / O के साथ नहीं) के 10000 पुनरावृत्तियों पर आधारित हैं ...

pure bash     3.174s (28 char IFS)
call (awk) 0m32.210s  (1 char IFS) 
call (tr)  0m32.178s  (1 char IFS) 

यहाँ आउटपुट है

# dlm_str  = :.~!@#$%^&()_+-=`}{][ ";></,
# original = :abc:.. def:.~!@#$%^&()_+-=`}{][ ";></,'single*quote?'..123:
# unified  = :abc::::def::::::::::::::::::::::::::::'single*quote?'::123:
# max-w 2^ = ::::::::::::::::
# shrunk.. = :abc:def:'single*quote?':123:
# arr[0] "abc"
# arr[1] "def"
# arr[2] "'single*quote?'"
# arr[3] "123"

यहाँ स्क्रिप्ट है

#!/bin/bash

# Note: This script modifies the source string. 
#       so work with a copy, if you need the original. 
# also: Use the name varG (Global) it's required by 'shrink_repeat_chars'
#
# NOTE: * asterisk      in IFS causes a regex(?) issue,     but  *  is ok in data. 
# NOTE: ? Question-mark in IFS causes a regex(?) issue,     but  ?  is ok in data. 
# NOTE: 0..9 digits     in IFS causes empty/wacky elements, but they're ok in data.
# NOTE: ' single quote  in IFS; don't know yet,             but  '  is ok in data.
# 
function shrink_repeat_chars () # A 'tr -s' analog
{
  # Shrink repeating occurrences of char
  #
  # $1: A string of delimiters which when consecutively repeated and are       
  #     considered as a shrinkable group. A example is: "   " whitespace delimiter.
  #
  # $varG  A global var which contains the string to be "shrunk".
  #
# echo "# dlm_str  = $1" 
# echo "# original = $varG" 
  dlms="$1"        # arg delimiter string
  dlm1=${dlms:0:1} # 1st delimiter char  
  dlmw=$dlm1       # work delimiter  
  # More than one delimiter char
  # ============================
  # When a delimiter contains more than one char.. ie (different byte` values),    
  # make all delimiter-chars in string $varG the same as the 1st delimiter char.
  ix=1;xx=${#dlms}; 
  while ((ix<xx)) ; do # Where more than one delim char, make all the same in varG  
    varG="${varG//${dlms:$ix:1}/$dlm1}"
    ix=$((ix+1))
  done
# echo "# unified  = $varG" 
  #
  # Binary shrink
  # =============
  # Find the longest required "power of 2' group needed for a binary shrink
  while [[ "$varG" =~ .*$dlmw$dlmw.* ]] ; do dlmw=$dlmw$dlmw; done # double its length
# echo "# max-w 2^ = $dlmw"
  #
  # Shrik groups of delims to a single char
  while [[ ! "$dlmw" == "$dlm1" ]] ; do
    varG=${varG//${dlmw}$dlm1/$dlm1}
    dlmw=${dlmw:$((${#dlmw}/2))}
  done
  varG=${varG//${dlmw}$dlm1/$dlm1}
# echo "# shrunk.. = $varG"
}

# Main
  varG=':abc:.. def:.~!@#$%^&()_+-=`}{][ ";></,'\''single*quote?'\''..123:' 
  sfi="$IFS"; IFS=':.~!@#$%^&()_+-=`}{][ ";></,' # save original IFS and set new multi-char IFS
  set -f                                         # disable globbing
  shrink_repeat_chars "$IFS" # The source string name must be $varG
  arr=(${varG:1})    # Strip leading dlim;  A single trailing dlim is ok (strangely
  for ix in ${!arr[*]} ; do  # Dump the array
     echo "# arr[$ix] \"${arr[ix]}\""
  done
  set +f     # re-enable globbing   
  IFS="$sfi" # re-instate the original IFS
  #
exit

बढ़िया काम, दिलचस्प +1!
एफ। हौरी

1

आप इसे gawk के साथ भी कर सकते हैं, लेकिन यह बहुत सुंदर नहीं है:

var=":abc::def:::ghi::::"
out=$( gawk -F ':+' '
  {
    # strip delimiters from the ends of the line
    sub("^"FS,"")
    sub(FS"$","")
    # then output in a bash-friendly format
    for (i=1;i<=NF;i++) printf("\"%s\" ", $i)
    print ""
  }
' <<< "$var" )
eval arr=($out)
for x in ${!arr[*]} ; do
  echo "# arr[$x] \"${arr[x]}\""
done

आउटपुट

# arr[0] "abc"
# arr[1] "def"
# arr[2] "ghi"

धन्यवाद ... मुझे लगता है कि मेरे मुख्य अनुरोध (संशोधित प्रश्न) में यह स्पष्ट नहीं है ... यह सिर्फ मेरे $varलिए इसे बदलकर करना काफी आसान है ${var##:}... मैं वास्तव में IFS को खुद को ट्विक करने के एक तरीके के बाद था .. मैं चाहता हूं एक बाहरी कॉल के बिना ऐसा करने के लिए (मुझे लगता है कि बैश किसी भी बाहरी कैन की तुलना में अधिक प्रभावी ढंग से कर सकता है .. इसलिए मैं उस ट्रैक पर रहूँगा) ... आपकी विधि काम करती है (+1) .... जहाँ तक इनपुट को संशोधित करने के रूप में, मैं इसे awk या tr के बजाय bash के साथ आज़माना चाहूंगा (यह सिस्टम कॉल से बचता है), लेकिन मैं वास्तव में IFS tweak के लिए बाहर लटका रहा हूं ...
पीटर।

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

@glen .. (आपने कहा कि आपका उत्तर "सुंदर" नहीं है .. मुझे लगता है कि यह है! :) हालांकि, मैंने एक सभी बैश संस्करण (बनाम एक बाहरी कॉल) को एक साथ रखा है और केवल एरे के निर्माण के 10000 पुनरावृत्तियों पर आधारित है () कोई आई / ओ) ... bash 1.276s... call (awk) 0m32.210s,,, call (tr) 0m32.178s... Do है कि एक बार और कुछ आप सोच सकते हैं बैश धीमी है! ... क्या इस मामले में जागना आसान है? ... अगर आपने पहले ही स्निपेट प्राप्त नहीं किया है :) ... मैं इसे बाद में पोस्ट करूँगा; अब जाना चाहिए।
पीटर.ओ.

वैसे, अपने gkk स्क्रिप्ट को फिर से ... मैं मूल रूप से पहले awk का उपयोग नहीं किया है, इसलिए मैं इसे (और अन्य) विस्तार से देख रहा हूं ... मैं क्यों नहीं चुन सकता, लेकिन मैं उल्लेख करूंगा मुद्दे को किसी भी तरह .. जब उद्धृत डेटा दिया जाता है, तो यह उद्धरण खो देता है, और उद्धरणों के बीच रिक्त स्थान पर विभाजन करता है .. और विषम संख्या में उद्धरणों के लिए क्रैश होता है ... यहां परीक्षण डेटा है:var="The \"X\" factor:::A single '\"' crashes:::\"One Two\""
पीटर

-1

इसका सरल उत्तर है: सभी सीमांकक को एक (पहले) को ध्वस्त करें।
इसके लिए लूप की आवश्यकता होती है (जो log(N)समय से कम चलता है ):

 var=':a bc::d ef:#$%_+$$%      ^%&*(*&*^
 $#,.::ghi::*::'                           # a long test string.
 d=':@!#$%^&*()_+,.'                       # delimiter set
 f=${d:0:1}                                # first delimiter
 v=${var//["$d"]/"$f"};                    # convert all delimiters to
 :                                         # the first of the delimiter set.
 tmp=$v                                    # temporal variable (v).
 while
     tmp=${tmp//["$f"]["$f"]/"$f"};        # collapse each two delimiters to one
     [[ "$tmp" != "$v" ]];                 # If there was a change
 do
     v=$tmp;                               # actualize the value of the string.
 done

यह सब करने के लिए छोड़ दिया है सही ढंग से एक सीमांकक पर स्ट्रिंग को विभाजित करने के लिए , और इसे प्रिंट करें:

 readarray -td "$f" arr < <(printf '%s%s' "$v"'' "$f")
 printf '<%s>' "${arr[@]}" ; echo

set -fआईएफएस को बदलने के लिए न तो जरूरत है और न ही।
रिक्त स्थान, newlines और ग्लोब पात्रों के साथ परीक्षण किया गया। सारा काम। काफी धीमा (एक शेल लूप होने की उम्मीद की जानी चाहिए)।
लेकिन केवल बैश के लिए (बैश 4.4+ विकल्प के कारण-d रीडअरे के कारण)।


एक शेल संस्करण एक सरणी का उपयोग नहीं कर सकता है, केवल उपलब्ध सरणी स्थितीय पैरामीटर हैं।
उपयोग करना tr -sकेवल एक पंक्ति है (IFS स्क्रिप्ट में नहीं बदलता है):

 set -f; IFS=$f command eval set -- '$(echo "$var" | tr -s "$d" "[$f*]" )""'

और इसे प्रिंट करें:

 printf '<%s>' "$@" ; echo

अभी भी धीमा है, लेकिन ज्यादा नहीं।

commandबॉर्न में कमांड अमान्य है।
Zsh में, commandकेवल बाहरी आदेशों को कॉल करता है और यदि commandउपयोग किया जाता है तो निष्फल विफल हो जाता है।
Ksh में, यहां तक ​​कि command, IFS के मूल्य को वैश्विक दायरे में बदल दिया जाता है।
और commandmksh से संबंधित गोले (mksh, lksh, पॉश) में विभाजन को विफल commandबनाता है कमांड को हटाने से कोड अधिक गोले पर चलता है। लेकिन: हटाने commandसे IFS अधिकांश गोले में अपना मूल्य बनाए रखेगा (eval एक विशेष बिलिन है) को छोड़कर बश में (पॉज़िक्स मोड के बिना) और डिफॉल्ट (नो इम्यूलेशन) मोड में zsh। इस अवधारणा को डिफ़ॉल्ट zsh में या तो साथ या बिना काम करने के लिए नहीं बनाया जा सकता है command


एकाधिक वर्ण IFS

हां, IFS बहु चरित्र वाला हो सकता है, लेकिन प्रत्येक वर्ण एक तर्क उत्पन्न करेगा:

 set -f; IFS="$d" command eval set -- '$(echo "$var" )""'
 printf '<%s>' "$@" ; echo

उत्पादन होगा:

 <><a bc><><d ef><><><><><><><><><      ><><><><><><><><><
 ><><><><><><ghi><><><><><>

बैश के साथ, आप commandश / POSIX अनुकरण में नहीं होने पर शब्द को छोड़ सकते हैं । आदेश ksh93 में विफल हो जाएगा (IFS परिवर्तित मूल्य रखता है)। Zsh में कमांड commandzsh को खोजने की कोशिश करता हैeval बाहरी कमांड के रूप में करता है (जो इसे नहीं ) और विफल हो जाता है।

क्या होता है कि केवल एक IFS वर्ण जो एक सीमांकक के लिए स्वतः ढह गए हैं IFS सफेद स्थान हैं।
IFS में एक स्थान एक के बाद एक लगातार सभी स्थानों को ध्वस्त कर देगा। एक टैब सभी टैब को ध्वस्त कर देगा। एक स्थान और एक टैब एक सीमांकक के लिए रिक्त स्थान और / या टैब को चलाता है। नईलाइन के साथ विचार को दोहराएं।

कई सीमांकक को ढहाने के लिए आसपास कुछ करतब दिखाने की आवश्यकता होती है।
इनपुट में ASCII 3 (0x03) का उपयोग नहीं किया गया है var:

 var=${var// /$'\3'}                       # protect spaces
 var=${var//["$d"]/ }                      # convert all delimiters to spaces
 set -f;                                   # avoid expanding globs.
 IFS=" " command eval set -- '""$var""'    # split on spaces.
 set -- "${@//$'\3'/ }"                    # convert spaces back.

Ksh, zsh और bash के बारे में अधिकांश टिप्पणियां (के बारे में) command और IFS) के अभी भी यहां लागू होती हैं।

$'\0'पाठ इनपुट में मान कम संभावित होगा, लेकिन बैश चर में NULs ( 0x00) नहीं हो सकता है ।

समान स्ट्रिंग ऑपरेशन करने के लिए श में कोई आंतरिक कमांड नहीं हैं, इसलिए sh स्क्रिप्ट के लिए एकमात्र समाधान है।


हां, मैंने लिखा है कि शेल के लिए ओपी ने पूछा: बैश। उस शेल में IFS नहीं रखा गया है। और हां, उदाहरण के लिए, zsh को पोर्टेबल नहीं है। @ स्टीफनचेज़ेलैस
इसहाक

बैश और zsh के मामले में, वे POSIX के रूप में व्यवहार करते हैं जब श के रूप में निर्दिष्ट किया जाता है
स्टीफन चेज़लस

@ StéphaneChazelas प्रत्येक शेल की सीमाओं के बारे में (कई) नोट जोड़े गए।
इसहाक

@ स्टीफनचैलेजलस डाउनवोट क्यों?
इसहाक

पता नहीं, मुझे नहीं था। BTW, मुझे लगता है command evalकि गिलेस द्वारा IIRC के बारे में एक समर्पित प्रश्नोत्तर यहाँ है
स्टीफन चेज़लस
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.