एक कथन के लिए IFS सेट करना


42

मुझे पता है कि एक एकल कमांड / बिल्ट-इन के दायरे के लिए एक कस्टम IFS मूल्य निर्धारित किया जा सकता है। क्या एक कथन के लिए एक कस्टम IFS मान सेट करने का कोई तरीका है ?? जाहिरा तौर पर नहीं, क्योंकि नीचे वैश्विक आधारित IFS मूल्य आधारित है जब यह प्रयास किया जाता है

#check environment IFS value, it is space-tab-newline
printf "%s" "$IFS" | od -bc
0000000 040 011 012
             \t  \n
0000003
#invoke built-in with custom IFS
IFS=$'\n' read -r -d '' -a arr <<< "$str"
#environment IFS value remains unchanged as seen below
printf "%s" "$IFS" | od -bc
0000000 040 011 012
             \t  \n
0000003

#now attempt to set IFS for a single statement
IFS=$'\n' a=($str)
#BUT environment IFS value is overwritten as seen below
printf "%s" "$IFS" | od -bc
0000000 012
         \n
     0000001

जवाबों:


39

कुछ गोले में (सहित bash):

IFS=: command eval 'p=($PATH)'

(साथ में bash, commandयदि आप श / POSIX एमुलेशन में नहीं हैं तो इसे छोड़ सकते हैं )। लेकिन सावधान रहें कि जब अछूता चर का उपयोग करते हैं, तो आपको आम तौर पर भी इसकी आवश्यकता होती है set -f, और अधिकांश गोले में इसके लिए कोई स्थानीय गुंजाइश नहीं होती है।

Zsh के साथ, आप कर सकते हैं:

(){ local IFS=:; p=($=PATH); }

$=PATHशब्द विभाजन को मजबूर करने के लिए किया जाता है जो डिफ़ॉल्ट रूप से नहीं किया जाता है zsh(चर विस्तार पर ग्लोबिंग नहीं किया जाता है, इसलिए आपको set -fतब तक ज़रूरत नहीं है जब तक कि श एम्यूलेशन में न हो)।

(){...}(या function {...}) को अनाम फ़ंक्शंस कहा जाता है और आम तौर पर स्थानीय स्कोप सेट करने के लिए उपयोग किया जाता है। कार्यों में स्थानीय गुंजाइश का समर्थन करने वाले अन्य गोले के साथ, आप कुछ ऐसा कर सकते हैं:

e() { eval "$@"; }
e 'local IFS=:; p=($PATH)'

POSIX गोले में चर और विकल्पों के लिए एक स्थानीय गुंजाइश को लागू करने के लिए, आप https://github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh पर दिए गए कार्यों का उपयोग कर सकते हैं । तब आप इसका उपयोग कर सकते हैं:

. /path/to/locvar.sh
var=3,2,2
call eval 'locvar IFS; locopt -f; IFS=,; set -- $var; a=$1 b=$2 c=$3'

(वैसे, यह $PATHउस तरह से विभाजित करने के लिए अमान्य है zshजैसे कि अन्य गोले में, आईएफएस फ़ील्ड सीमांकक है, फ़ील्ड पृथक्करण नहीं)।

IFS=$'\n' a=($str)

क्या सिर्फ दो असाइनमेंट हैं, एक के बाद एक जैसे a=1 b=2

इस पर स्पष्टीकरण का एक नोट var=value cmd:

में:

var=value cmd arg

खोल कार्यान्वित /path/to/cmdएक नई प्रक्रिया और पास में cmdऔर argमें argv[]और var=valueमें envp[]। यह वास्तव में एक चर असाइनमेंट नहीं है, लेकिन निष्पादित कमांड के लिए अधिक पासिंग वातावरण चर है । बॉर्न या कॉर्न शेल में set -k, आप इसे लिख भी सकते हैं cmd var=value arg

अब, यह उन निर्माणों या कार्यों पर लागू नहीं होता है जिन्हें निष्पादित नहीं किया जाता है । बॉर्न शेल में var=value some-builtin, varअंत में , var=valueअकेले की तरह सेट किया जा रहा है । उदाहरण के लिए इसका मतलब है कि var=value echo foo(जो उपयोगी नहीं है) का व्यवहार इस बात पर निर्भर करता है कि echoबिल्टिन है या नहीं।

POSIX और / या kshउसमें बदलाव किया गया है कि उस बॉर्न व्यवहार में केवल विशेष बिल्डिन नामक बिल्डरों की श्रेणी के लिए होता है । evalएक विशेष बिलिन है, readनहीं है। गैर विशेष बिल्डिन के लिए, केवल बिलिन के निष्पादन के लिए var=value builtinसेट करता है , varजो बाहरी कमांड चलाने के दौरान समान व्यवहार करता है।

commandआदेश दूर करने के लिए इस्तेमाल किया जा सकता विशेष उन की विशेषता विशेष builtins । हालांकि POSIX ने अनदेखी की है कि evalऔर .बिल्डरों के लिए इसका मतलब है कि गोले को एक चर स्टैक को लागू करना होगा (भले ही यह आदेशों को सीमित localया typesetगुंजाइश को निर्दिष्ट नहीं करता है ), क्योंकि आप कर सकते हैं:

a=0; a=1 command eval 'a=2 command eval echo \$a; echo $a'; echo $a

या और भी:

a=1 command eval myfunction

साथ myfunctionएक समारोह किया जा रहा है का उपयोग कर या स्थापित करने $aऔर संभावित रूप से बुला command eval

यह वास्तव में एक अनदेखी थी क्योंकि ksh(जो कल्पना ज्यादातर पर आधारित है) ने इसे लागू नहीं किया (और एटी एंड टी kshऔर zshअभी भी नहीं), लेकिन आजकल, उन दो को छोड़कर, अधिकांश गोले इसे लागू करते हैं। व्यवहार शेल में भिन्न होता है, हालांकि चीजों में:

a=0; a=1 command eval a=2; echo "$a"

हालांकि। localगोले का उपयोग करना जो इसका समर्थन करता है, स्थानीय दायरे को लागू करने का अधिक विश्वसनीय तरीका है।


अजीब रूप से, केवल डैश, पीडीएस और बैश में POSIX द्वारा अनिवार्य के रूप में , की अवधि के लिए IFS=: command eval …सेट करता है , लेकिन ksh 93u में नहीं। यह देखने के लिए असामान्य है कि ksh को ऑड-नॉन-कंप्लेंट-वन-आउट किया जा रहा है। IFSeval
गिल्स एसओ- बुराई को रोकें '

12

कर्निघन और पाइक द्वारा "द यूनिक्स प्रोग्रामिंग पर्यावरण" से लिया गया मानक सेव-एंड-रिस्टोर:

#!/bin/sh
old_IFS=$IFS
IFS="something_new"
some_program_or_builtin
IFS=${old_IFS}

2
धन्यवाद और +1। हां मुझे इस विकल्प के बारे में पता है, लेकिन मैं जानना चाहूंगा कि क्या "क्लीनर" विकल्प है अगर आपको पता है कि मेरा क्या मतलब है
iruvar

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

9
$IFSयदि वह पहले से परेशान था, तो वह सही तरीके से पुनर्स्थापित करने में विफल रहता है ।
स्टीफन चेज़लस

2
यदि यह परेशान नहीं है , तो बैश इसका इलाज करता है $'\t\n'' ', जैसा कि यहां बताया गया है: wiki.bash-hackers.org/syntax/expansion/…
davide

2
@ डेविड, वह होगा $' \t\n'। अंतरिक्ष के लिए पहले इस्तेमाल किया जा सकता है "$*"। ध्यान दें कि यह सभी बॉर्न जैसे गोले में समान है।
स्टीफन चेज़लस

8

अपनी स्क्रिप्ट को एक फंक्शन में रखें और कमांड लाइन आर्ग्युमेंट्स को पास करने वाले फंक्शन को इनवाइट करें। जैसा कि IFS को स्थानीय परिभाषित किया गया है, इसमें होने वाले परिवर्तन वैश्विक IFS को प्रभावित नहीं करते हैं।

main() {
  local IFS='/'

  # the rest goes here
}

main "$@"

6

इस आदेश के लिए:

IFS=$'\n' a=($str)

एक वैकल्पिक समाधान है: पहले असाइनमेंट ( IFS=$'\n') को निष्पादित करने के लिए एक कमांड (एक फ़ंक्शन) देने के लिए:

$ split(){ a=( $str ); }
$ IFS=$'\n' split

यह विभाजन को कॉल करने के लिए पर्यावरण में IFS डाल देगा, लेकिन वर्तमान परिवेश में इसे बरकरार नहीं रखा जाएगा।

यह भी eval के हमेशा जोखिम भरे उपयोग से बचा जाता है।


PshIX मोड में ksh93 और mksh, और bash और zsh में, जो अभी भी POSIX द्वारा आवश्यकतानुसार बाद में $IFSसेट होता है $'\n'
स्टीफन चेज़लस

4

@Helpermethod से प्रस्तावित उत्तर निश्चित रूप से एक दिलचस्प दृष्टिकोण है। लेकिन यह भी एक छोटा सा जाल है क्योंकि BASH में लोकल वैरिएबल स्कोप कॉलर से लेकर फंक्शन तक फैली हुई है। इसलिए, IFS को मुख्य () में सेट करने से, उस मूल्य का परिणाम मुख्य () से बुलाए गए कार्यों से बनेगा। यहाँ एक उदाहरण है:

#!/usr/bin/env bash
#
func() {
  # local IFS='\'

  local args=${@}
  echo -n "$FUNCNAME A"
  for ((i=0; i<${#args[@]}; i++)); do
    printf "[%s]: %s" "${i}" "${args[$i]}"
  done
  echo

  local f_args=( $(echo "${args[0]}") )
  echo -n "$FUNCNAME B"
  for ((i=0; i<${#f_args[@]}; i++)); do
    printf "[%s]: %s" "${i}" "${f_args[$i]}  "
  done
  echo
}

main() {
  local IFS='/'

  # the rest goes here
  local args=${@}
  echo -n "$FUNCNAME A"
  for ((i=0; i<${#args[@]}; i++)); do
    printf "[%s]: %s" "${i}" "${args[$i]}"
  done
  echo

  local m_args=( $(echo "${args[0]}") )
  echo -n "$FUNCNAME B"
  for ((i=0; i<${#m_args[@]}; i++)); do
    printf "[%s]: %s" "${i}" "${m_args[$i]}  "
  done
  echo

  func "${m_args[*]}"
}

main "$@"

और उत्पादन ...

main A[0]: ick/blick/flick
main B[0]: ick  [1]: blick  [2]: flick
func A[0]: ick/blick/flick
func B[0]: ick  [1]: blick  [2]: flick

यदि मुख्य () में घोषित IFS अभी भी फंक () में गुंजाइश नहीं थी, तो एरेसी में सरणी को ठीक से पार्स नहीं किया गया होगा (बी) फंक में पहली पंक्ति को हटा दें () और आपको यह आउटपुट मिलता है:

main A[0]: ick/blick/flick
main B[0]: ick  [1]: blick  [2]: flick
func A[0]: ick/blick/flick
func B[0]: ick/blick/flick

अगर आपको IFS के दायरे से बाहर जाना पड़ा तो आपको क्या करना चाहिए।

एक बेहतर समाधान IMHO, वैश्विक / स्थानीय स्तर पर IFS में परिवर्तन या भरोसा करने से गुजरना है। इसके बजाय, वहां एक नया शेल और आईएफएस के साथ फिडेल करें। उदाहरण के लिए, यदि आप मुख्य रूप से फंक () को कॉल करते हैं, तो सरणी को एक बैकवर्ड स्लैश फ़ील्ड विभाजक के साथ स्ट्रिंग के रूप में पास करना:

func $(IFS='\'; echo "${m_args[*]}")

... IFS में यह परिवर्तन फंक () के भीतर परिलक्षित नहीं होगा। सरणी को स्ट्रिंग के रूप में पारित किया जाएगा:

ick\blick\flick

... लेकिन फ़ंक के अंदर () IFS अभी भी "/" होगा (जैसा कि मुख्य रूप से सेट किया गया है ()) जब तक कि फंक () में स्थानीय रूप से नहीं बदला जाता।

IFS में परिवर्तनों को अलग करने के बारे में अधिक जानकारी निम्न लिंक पर देखी जा सकती है:

कैसे मैं एक बैश सरणी चर को एक नई सीमा के साथ सीमांकित स्ट्रिंग में परिवर्तित करूं?

IFS के साथ सरणी के लिए बैश स्ट्रिंग

सामान्य शेल स्क्रिप्ट प्रोग्रामिंग के लिए संकेत और सुझाव - "उप-गोले के उपयोग पर ध्यान दें ..." देखें


वास्तव में दिलचस्प ...
21

"IFS के साथ सरणी के लिए बैश स्ट्रिंग" IFS=$'\n' declare -a astr=(...)सही धन्यवाद!
कुंभ राशि

1

सवाल से यह टुकड़ा:

IFS=$'\n' a=($str)

दो अलग-अलग वैश्विक चर असाइनमेंट के रूप में बाएं से दाएं मूल्यांकन किया जाता है, और इसके बराबर है:

IFS=$'\n'; a=($str)

या

IFS=$'\n'
a=($str)

यह बताता है कि वैश्विक क्यों IFSसंशोधित किया गया था, और $strसरणी तत्वों में शब्द-विभाजन को नए मूल्य का उपयोग करके क्यों प्रदर्शित किया गया था IFS

आप IFSइस तरह के संशोधन के प्रभाव को सीमित करने के लिए एक उपधारा का उपयोग करने के लिए परीक्षा हो सकती है :

str="value 0:value 1"
a=( old values )
( # Following code runs in a subshell
 IFS=":"
 a=($str)
 printf 'Subshell IFS: %q\n' "${IFS}"
 echo "Subshell: a[0]='${a[0]}' a[1]='${a[1]}'"
)
printf 'Parent IFS: %q\n' "${IFS}"
echo "Parent: a[0]='${a[0]}' a[1]='${a[1]}'"

लेकिन आप जल्दी से ध्यान देंगे कि का संशोधन aभी उपधारा तक सीमित है:

Subshell IFS: :
Subshell: a[0]='value 0' a[1]='value 1'
Parent IFS: $' \t\n'
Parent: a[0]='old' a[1]='values'

इसके बाद, आपको @msw द्वारा इस पिछले उत्तर से समाधान का उपयोग करके IFS को बचाने / पुनर्स्थापित करने या local IFSकिसी फ़ंक्शन के अंदर @helpermethod द्वारा सुझाए गए तरीके का उपयोग करने की कोशिश की जाएगी। लेकिन बहुत जल्द, आप नोटिस करते हैं कि आप हर तरह की परेशानी में हैं, खासकर यदि आप एक पुस्तकालय लेखक हैं, जो इनवॉइसिंग स्क्रिप्ट का दुरुपयोग करने के खिलाफ मजबूत होना चाहिए:

  • क्या होगा अगर IFSशुरू में परेशान था?
  • अगर हम set -u(उर्फ set -o nounset) साथ चल रहे हैं तो क्या होगा ?
  • क्या हुआ अगर IFSकेवल के माध्यम से पढ़ा गया था declare -r IFS?
  • क्या होगा यदि मुझे पुनरावृत्ति और या अतुल्यकालिक निष्पादन (जैसे trapहैंडलर`) के साथ काम करने के लिए सेव / रिस्टोर तंत्र की आवश्यकता है ?

कृपया IFS को सहेजें / पुनर्स्थापित न करें। इसके बजाय, अस्थायी संशोधनों से चिपके रहें:

  • किसी एकल कमांड के लिए चर संशोधन को सीमित करने के लिए, अंतर्निहित या फ़ंक्शन मंगलाचरण का उपयोग करें IFS="value" command

    • किसी विशिष्ट वर्ण ( :उदाहरण के रूप में नीचे) पर विभाजित करके कई चर में पढ़ने के लिए, उपयोग करें:

      IFS=":" read -r var1 var2 <<< "$str"
    • सरणी उपयोग में पढ़ने के लिए (इसके बजाय ऐसा करें array_var=( $str )):

      IFS=":" read -r -a array_var <<< "$str"
  • चर को उप-सीमा में संशोधित करने के प्रभावों को सीमित करें।

    • किसी सरणी के तत्वों को अल्पविराम द्वारा अलग करने के लिए:

      (IFS=","; echo "${array[*]}")
    • एक स्ट्रिंग में उस पर कब्जा करने के लिए:

      csv="$(IFS=","; echo "${array[*]}")"

0

सबसे सीधे फॉरवर्ड समाधान मूल की एक प्रति लेना है $IFS, जैसे कि msw का उत्तर। हालांकि, यह समाधान एक शुरुआत IFSऔर एक IFSसेट के बीच अंतर नहीं करता है जो खाली स्ट्रिंग के बराबर है, जो कई अनुप्रयोगों के लिए महत्वपूर्ण है। यहाँ एक और सामान्य समाधान है जो इस भेद को पकड़ता है:

# Functions taking care of IFS
set_IFS(){
    if [ -z "${IFS+x}" ]; then
        IFS_ori="__unset__"
    else
        IFS_ori="$IFS"
    fi
    IFS="$1"
}
reset_IFS(){
    if [ "${IFS_ori}" == "__unset__" ]; then
        unset IFS
    else
        IFS="${IFS_ori}"
    fi
}

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