आईएफएस को समझना


71

इस साइट और StackOverflow पर निम्नलिखित कुछ सूत्र यह समझने में मददगार हैं कि कैसे IFSकाम करता है:

लेकिन मेरे पास अभी भी कुछ छोटे सवाल हैं। मैंने उन्हें उसी पोस्ट में पूछने का फैसला किया क्योंकि मुझे लगता है कि इससे भविष्य के पाठकों को बेहतर मदद मिल सकती है:

Q1। IFSआम तौर पर "फ़ील्ड विभाजन" के संदर्भ में चर्चा की जाती है। क्या क्षेत्र विभाजन शब्द विभाजन के समान है ?

Q2: POSIX विनिर्देश कहता है :

यदि IFS का मान शून्य है, तो कोई फ़ील्ड विभाजन नहीं किया जाएगा।

क्या अशक्त IFS=करने के IFSलिए सेटिंग के समान है ? क्या इसका मतलब यह है कि इसे empty stringभी सेट करने से ?

Q3: POSIX विनिर्देश में, मैंने निम्नलिखित पढ़ा :

यदि IFS सेट नहीं है, तो शेल ऐसा व्यवहार करेगा जैसे कि IFS का मान है <space>, <tab> and <newline>

मान लीजिए कि मैं डिफ़ॉल्ट मान को पुनर्स्थापित करना चाहता हूं IFS। मैं उसको कैसे करू? (खासतौर पर, कैसे मैं किससे संबंधित होते हैं <tab>और <newline>?)

Q4: अंत में, यह कोड कैसे होगा:

while IFS= read -r line
do    
    echo $line
done < /path_to_text_file

व्यवहार करें कि क्या हम पहली पंक्ति को बदलते हैं

while read -r line # Use the default IFS value

या इसमें:

while IFS=' ' read -r line

जवाबों:


28
  1. हाँ, वे समान हैं।
  2. हाँ।
  3. बैश, और इसी तरह के गोले में, आप कुछ ऐसा कर सकते हैं IFS=$' \t\n'। अन्यथा, आप का उपयोग करके शाब्दिक नियंत्रण कोड सम्मिलित कर सकते हैं [space] CTRL+V [tab] CTRL+V [enter]। यदि आप ऐसा करने की योजना बना रहे हैं, हालांकि, पुराने IFSमूल्य को अस्थायी रूप से संग्रहीत करने के लिए किसी अन्य चर का उपयोग करना बेहतर है , और फिर इसे बाद में पुनर्स्थापित करें (या अस्थायी रूप से var=foo commandसिंटैक्स का उपयोग करके इसे एक आदेश के लिए ओवरराइड करें )।
    • पहला कोड स्निपेट पूरी लाइन रीड, वर्बटीम में डाल देगा $line, क्योंकि शब्द विभाजन के लिए कोई फ़ील्ड विभाजक नहीं हैं। हालांकि यह ध्यान में रखें कि चूंकि कई गोले स्ट्रिंग्स को स्टोर करने के लिए क्रेस्टिंग का उपयोग करते हैं, इसलिए एनयूएल का पहला उदाहरण अभी भी प्रकट होने का कारण हो सकता है।
    • दूसरा कोड स्निपेट इनपुट की सटीक प्रतिलिपि नहीं डाल सकता है $line। उदाहरण के लिए, यदि कई लगातार क्षेत्र विभाजक हैं, तो उन्हें पहले तत्व के एकल उदाहरण में बनाया जाएगा। यह अक्सर आसपास के व्हाट्सएप के नुकसान के रूप में पहचाना जाता है।
    • तीसरा कोड स्निपेट दूसरे के समान ही करेगा, सिवाय इसके कि यह केवल एक स्थान पर विभाजित होगा (सामान्य स्थान, टैब या न्यूलाइन नहीं)।

3
Q2 का उत्तर गलत है: एक खाली IFSऔर एक परेशान IFSबहुत अलग है। Q4 का उत्तर आंशिक रूप से गलत है: आंतरिक विभाजकों को यहां स्पर्श नहीं किया जाता है, केवल प्रमुख और अनुगामी होते हैं।
गिल्स

3
@ गिल्स: Q2 में दिए गए तीन संप्रदायों में से कोई भी एक परेशान को संदर्भित नहीं करता है IFS, उन सभी का मतलब है IFS=
स्टीफन जिमेनेज

@ गाइल्स Q2 में, मैंने कभी नहीं कहा कि वे समान थे। और आंतरिक विभाजकों को छुआ जाता है, जैसा कि यहां दिखाया गया है IFS=' ' ; foo=( bar baz qux ) ; echo "${#foo[@]}":। (एर, क्या? वहाँ कई जगह सीमांकक होना चाहिए, एसओ इंजन उन्हें अलग करता रहता है)।
क्रिस डाउन

2
@ StéphaneGimenez, क्रिस: ओह, सही है, Q2 के बारे में क्षमा करें, मैंने प्रश्न को गलत बताया। Q4 के लिए, हम बात कर रहे हैं read; अंतिम चर वह सब कुछ छोड़ देता है जो अंतिम विभाजक को छोड़ कर अंदर के विभाजकों को छोड़ देता है।
गिल्स

1
गिल्स आंशिक रूप से उन रिक्त स्थान के बारे में सही है, जिन्हें पढ़कर नहीं हटाया जा रहा है। विवरण के लिए मेरा उत्तर पढ़ें।

22

Q1: हाँ। "फ़ील्ड विभाजन" और "शब्द विभाजन" एक ही अवधारणा के लिए दो शब्द हैं।

Q2: हाँ। यदि IFSकोई परेशान है (यानी बाद में unset IFS), तो यह IFSसेट किया जा रहा है $' \t\n'(एक स्थान, एक टैब और एक नई पंक्ति)। यदि IFSइसे एक खाली मान पर सेट किया जाता है (जो कि "अशक्त" का अर्थ यहाँ है) (यानी बाद IFS=या IFS=''या तो IFS=""), कोई भी क्षेत्र विभाजन बिल्कुल नहीं किया जाता है (और $*, जो सामान्य रूप से पहले वर्ण का $IFSउपयोग करता है, एक अंतरिक्ष वर्ण का उपयोग करता है)।

Q3: यदि आप डिफ़ॉल्ट IFSव्यवहार करना चाहते हैं, तो आप उपयोग कर सकते हैं unset IFS। यदि आप IFSइस डिफ़ॉल्ट मान को स्पष्ट रूप से सेट करना चाहते हैं , तो आप सिंगल कोट्स में शाब्दिक वर्णों की जगह, टैब, न्यूलाइन डाल सकते हैं। Ksh93, bash या zsh में, आप उपयोग कर सकते हैं IFS=$' \t\n'। विशेष रूप से, यदि आप अपनी स्रोत फ़ाइल में शाब्दिक टैब वर्ण होने से बचना चाहते हैं, तो आप उपयोग कर सकते हैं

IFS=" $(echo t | tr t \\t)
"

Q4: IFSएक खाली मान पर read -r lineसेट lineकरने के साथ, इसकी समाप्ति नई पंक्ति को छोड़कर पूरी लाइन पर सेट हो जाती है। साथ IFS=" ", शुरुआत में और पंक्ति के अंत में रिक्त स्थान छंटनी कर रहे हैं। डिफ़ॉल्ट मान के साथ IFS, टैब और रिक्त स्थान छंटनी किए जाते हैं।


2
Q2 आंशिक रूप से गलत है। यदि IFS खाली है, तो "$ *" बिना विभाजक के जुड़ जाता है। (के लिए $@, गैर-सूची संदर्भों में गोले के बीच कुछ भिन्नताएं हैं IFS=; var=$@)। यह ध्यान दिया जाना चाहिए कि जब IFS खाली होता है, तो कोई भी शब्द विभाजित नहीं होता है, लेकिन $ var अभी भी खाली तर्क के बजाय कोई तर्क नहीं देता है जब $ var खाली होता है, और ग्लोबिंग अभी भी लागू होता है, इसलिए आपको अभी भी चर (यहां तक ​​कि आप को उद्धृत करने की आवश्यकता है) अक्षम
ग्लोबिंग

13

Q1। क्षेत्र विभाजन।

क्या क्षेत्र विभाजन शब्द विभाजन के समान है?

हां, दोनों एक ही विचार की ओर इशारा करते हैं।

Q2: IFS null कब है ?

क्या IFS=''अशक्त के रूप में एक ही सेटिंग है, एक खाली स्ट्रिंग के समान है?

हां, तीनों का अर्थ समान है: कोई क्षेत्र / शब्द विभाजन नहीं किया जाएगा। इसके अलावा, यह मुद्रण क्षेत्रों को प्रभावित करता है (जैसा कि echo "$*") सभी क्षेत्रों को बिना किसी स्थान के साथ एक साथ समतल किया जाएगा।

Q3: (एक भाग) परेशान IFS।

POSIX विनिर्देश में, मैंने निम्नलिखित पढ़ा :

यदि IFS सेट नहीं है, तो शेल ऐसा व्यवहार करेगा जैसे कि IFS का मान <space> <tab> <newline> है

जो बिल्कुल इसके बराबर है:

ए के साथ unset IFS, शेल ऐसा व्यवहार करेगा जैसे आईएफएस डिफ़ॉल्ट है।

इसका मतलब है कि 'फ़ील्ड विभाजन' एक डिफ़ॉल्ट IFS मान के साथ ठीक वैसा ही होगा, या परेशान नहीं होगा।
इसका मतलब यह नहीं है कि IFS सभी परिस्थितियों में उसी तरह काम करेगा। अधिक विशिष्ट होने के नाते, को क्रियान्वित करने OldIFS=$IFSवर सेट हो जाएगा OldIFSकरने के लिए शून्य , डिफ़ॉल्ट नहीं। और IFS को वापस सेट करने की कोशिश कर रहा है, क्योंकि यह IFS=OldIFSIFS को अशक्त करने के लिए सेट करेगा, इसे अप्रसन्न न रखें जैसा कि पहले था। ध्यान रहे !!।

Q3: (भाग b) IFS को पुनर्स्थापित करें।

मैं डिफ़ॉल्ट रूप से IFS का मान कैसे पुनर्स्थापित कर सकता हूं। मान लें कि मैं IFS के डिफ़ॉल्ट मान को पुनर्स्थापित करना चाहता हूं। मैं उसको कैसे करू? (अधिक विशेष रूप से, मैं <टैब> और <newline> का संदर्भ कैसे दूं ?]

Zsh, ksh और bash (AFAIK) के लिए, IFS को डिफ़ॉल्ट मान पर सेट किया जा सकता है:

IFS=$' \t\n'        # works with zsh, ksh, bash.

हो गया, आपको और कुछ नहीं पढ़ना चाहिए।

लेकिन अगर आपको sh के लिए IFS को फिर से सेट करना है, तो यह जटिल हो सकता है।

चलो कमियों (जटिलता को छोड़कर) के साथ आसानी से पूरा करने के लिए एक नज़र डालें।

1.- परेशान IFS।

हम बस unset IFS(पढ़ें Q3 हिस्सा एक, ऊपर)।

2.- स्वैप चार्ट।

वर्कअराउंड के रूप में, टैब और न्यूलाइन के मान को स्वैप करने से IFS का मान सेट करना आसान हो जाता है, और फिर यह बराबर तरीके से काम करता है।

IFS को <space> <newline> <tab> पर सेट करें :

sh -c 'IFS=$(echo " \n\t"); printf "%s" "$IFS"|xxd'      # Works.

3.- एक साधारण? उपाय:

अगर ऐसी बाल स्क्रिप्ट हैं, जिन्हें सही ढंग से IFS की आवश्यकता है, तो आप हमेशा स्वयं लिख सकते हैं:

आईएफएस = '   
'

जहां अनुक्रम मैन्युअल रूप से टाइप किया गया था: IFS='spacetabnewline'अनुक्रम, जो वास्तव में सही ढंग से ऊपर टाइप किया गया है (यदि आपको पुष्टि करने की आवश्यकता है, तो इस उत्तर को संपादित करें)। लेकिन आपके ब्राउज़र से एक कॉपी / पेस्ट टूट जाएगा क्योंकि ब्राउज़र व्हाट्सएप को निचोड़ / छिपा देगा। ऊपर लिखे अनुसार कोड को साझा करना मुश्किल हो जाता है।

4.- पूरा समाधान।

कोड लिखने के लिए जिसे सुरक्षित रूप से कॉपी किया जा सकता है, उसमें आमतौर पर अस्पष्ट प्रिंट करने योग्य भाग शामिल होते हैं।

हमें कुछ कोड की आवश्यकता है जो अपेक्षित मूल्य का "उत्पादन" करते हैं। लेकिन, भले ही वैचारिक रूप से सही हो, यह कोड एक अनुगामी निर्धारित नहीं करेगा \n:

sh -c 'IFS=$(echo " \t\n"); printf "%s" "$IFS"|xxd'      # wrong.

ऐसा इसलिए होता है, क्योंकि अधिकांश गोले के तहत, विस्तार पर कमांड $(...)या `...`कमांड प्रतिस्थापन की सभी नई रूपरेखाएँ हटा दी जाती हैं।

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

sh -c 'IFS="$(printf " \t\nx")"; IFS="${IFS%x}"; printf "$IFS"|xxd'  # Correct.

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

env IFS=$' \t\n' sh -c 'printf "%s" "$IFS"|xxd'

संक्षेप में, श काफी अजीब रोमांच को डिफ़ॉल्ट करने के लिए IFS को रीसेट करता है।

Q4: वास्तविक कोड में:

अंत में, यह कोड कैसे होगा:

while IFS= read -r line
do
    echo $line
done < /path_to_text_file

व्यवहार करें कि क्या हम पहली पंक्ति को बदलते हैं

while read -r line # Use the default IFS value

या इसमें:

while IFS=' ' read -r line

पहला: मैं नहीं जानता कि क्या echo $line(var नहीं उद्धृत के साथ) वहाँ पर है, या नहीं। यह 'फ़ील्ड विभाजन' के एक दूसरे स्तर का परिचय देता है जो कि पढ़ने के लिए नहीं है। तो मैं दोनों को जवाब दूंगा। :)

इस कोड के साथ (ताकि आप पुष्टि कर सकें)। आपको उपयोगी xxd की आवश्यकता होगी :

#!/bin/ksh
# Correctly set IFS as described above.
defIFS="$(printf " \t\nx")"; defIFS="${defIFS%x}";
IFS="$defIFS"
printf "IFS value: "
printf "%s" "$IFS"| xxd -p

a='   bar   baz   quz   '; l="${#a}"
printf "var value          : %${l}s-" "$a" ; printf "%s\n" "$a" | xxd -p

printf "%s\n" "$a" | while IFS='x' read -r line; do
    printf "IFS --x--          : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

printf 'Values      quoted :\n' ""  # With values quoted:
printf "%s\n" "$a" | while IFS='' read -r line; do
    printf "IFS null    quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

printf "%s\n" "$a" | while IFS="$defIFS" read -r line; do
    printf "IFS default quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

unset IFS; printf "%s\n" "$a" | while read -r line; do
    printf "IFS unset   quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;
    IFS="$defIFS"   # set IFS back to default.

printf "%s\n" "$a" | while IFS=' ' read -r line; do
    printf "IFS space   quoted : %${l}s-" "$line" ;
    printf "%s" "$line" |xxd -p; done;

printf '%s\n' "Values unquoted :"   # Now with values unquoted:
printf "%s\n" "$a" | while IFS='x' read -r line; do
    printf "IFS --x-- unquoted : "
    printf "%s, " $line; printf "%s," $line |xxd -p; done

printf "%s\n" "$a" | while IFS='' read -r line; do
    printf "IFS null  unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done

printf "%s\n" "$a" | while IFS="$defIFS" read -r line; do
    printf "IFS defau unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done

unset IFS; printf "%s\n" "$a" | while read -r line; do
    printf "IFS unset unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done
    IFS="$defIFS"   # set IFS back to default.

printf "%s\n" "$a" | while IFS=' ' read -r line; do
    printf "IFS space unquoted : ";
    printf "%s, " $line; printf "%s," $line |xxd -p; done

मुझे मिला:

$ ./stackexchange-Understanding-IFS.sh
IFS value: 20090a
var value          :    bar   baz   quz   -20202062617220202062617a20202071757a2020200a
IFS --x--          :    bar   baz   quz   -20202062617220202062617a20202071757a202020
Values      quoted :
IFS null    quoted :    bar   baz   quz   -20202062617220202062617a20202071757a202020
IFS default quoted :       bar   baz   quz-62617220202062617a20202071757a
IFS unset   quoted :       bar   baz   quz-62617220202062617a20202071757a
IFS space   quoted :       bar   baz   quz-62617220202062617a20202071757a
Values unquoted :
IFS --x-- unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS null  unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS defau unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS unset unquoted : bar, baz, quz, 6261722c62617a2c71757a2c
IFS space unquoted : bar, baz, quz, 6261722c62617a2c71757a2c

पहला मान सिर्फ सही मूल्य है IFS='spacetabnewline'

अगली पंक्ति सभी हेक्स मान है जो कि var $aमें है, और अंत में एक नई लाइन '0a' है क्योंकि यह प्रत्येक रीड कमांड को दिया जाने वाला है।

अगली पंक्ति, जिसके लिए IFS शून्य है, कोई 'फ़ील्ड विभाजन' नहीं करता है, लेकिन नई पंक्ति को हटा दिया जाता है (जैसा कि अपेक्षित है)।

अगली तीन पंक्तियाँ, क्योंकि IFS में एक स्थान होता है, प्रारंभिक रिक्त स्थान को हटा दें और शेष शेष के लिए var पंक्ति सेट करें।

अंतिम चार पंक्तियों से पता चलता है कि एक अछूता चर क्या करेगा। मानों को (कई) रिक्त स्थान पर विभाजित किया जाएगा और इसे निम्नानुसार मुद्रित किया जाएगा:bar,baz,qux,


4

unset IFS IFS को स्पष्ट करता है, भले ही इसके बाद IFS को "t \ n" माना जाए

$ echo "'$IFS'"
'   
'
$ IFS=""
$ echo "'$IFS'"
''
$ unset IFS
$ echo "'$IFS'"
''
$ IFS=$' \t\n'
$ echo "'$IFS'"
'   
'
$

एक ही व्यवहार के साथ बैश संस्करणों 4.2.45 और 3.2.25 पर परीक्षण किया गया।


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