काट-छाँट क्यों विफल हो जाती है और zsh नहीं?


10

मैं टैब-सीमांकित फ़ील्ड के साथ एक फ़ाइल बनाता हूं।

echo foo$'\t'bar$'\t'baz$'\n'foo$'\t'bar$'\t'baz > input

मेरे पास निम्नलिखित लिपि है zsh.sh

#!/usr/bin/env zsh
while read line; do
    <<<$line cut -f 2
done < "$1"

मैं इसका परीक्षण करता हूं।

$ ./zsh.sh input
bar
bar

यह ठीक काम करता है। हालाँकि, जब मैं बदले में पहली पंक्ति को बदलने के लिए bash, यह विफल रहता है।

$ ./bash.sh input
foo bar baz
foo bar baz

यह क्यों विफल होता है bashऔर इसके साथ काम करता है zsh?

अतिरिक्त समस्या निवारण

  • envउसी व्यवहार को उत्पन्न करने के बजाय शेबांग में सीधे रास्तों का उपयोग करना ।
  • echoयहां-स्ट्रिंग का उपयोग करने के बजाय पाइपिंग <<<$lineभी उसी व्यवहार का उत्पादन करता है। यानी echo $line | cut -f 2
  • दोनों गोले awkके cut काम के बजाय का उपयोग करना । यानी <<<$line awk '{print $2}'

4
वैसे, आप इन में से एक करके और अधिक बस अपने परीक्षण फ़ाइल बना सकते हैं: echo -e 'foo\tbar\tbaz\n...', echo $'foo\tbar\tbaz\n...', या printf 'foo\tbar\tbaz\n...\n'या इनमें से रूपों। यह आपको व्यक्तिगत रूप से प्रत्येक टैब या न्यूलाइन को लपेटने से बचाता है।
अगली सूचना तक रोक दिया गया।

जवाबों:


13

क्या होता है कि bashटैब को स्पेस से बदल देता है। आप "$line"इसके स्थान पर या स्पष्ट रूप से रिक्त स्थान काटकर इस समस्या से बच सकते हैं ।


1
क्या कोई कारण है कि बैश एक को देखता है \tऔर इसे एक स्थान के साथ बदल देता है?
user1717828

@ user1717828 हाँ, इसे थूक + ग्लोब ऑपरेटर कहा जाता है । यह तब होता है जब आप बैश और इसी तरह के गोले में एक चर का उपयोग करते हैं।
terdon

1
@terdon, में <<< $line, bashविभाजन करता है लेकिन ग्लोब में नहीं। वहाँ कोई कारण नहीं है कि यह <<<एक शब्द की उम्मीद के रूप में यहाँ विभाजित होगा । यह विभाजित हो जाता है और फिर उस मामले में शामिल हो जाता है, जो थोड़ा समझ में आता है और अन्य सभी शेल कार्यान्वयनों के खिलाफ है जो <<<पहले या बाद में समर्थन करते हैं bash। IMO यह एक बग है।
स्टीफन चेज़लस

@ स्टीफनचेज़लस मेला काफी, समस्या वैसे भी विभाजन के साथ है।
terdon

2
@ स्टीफनचेलजैस कोई विभाजन नहीं है (न ही ग्लोब) बाश 4.4 पर होता है

17

यही कारण है कि के दशक में क्योंकि <<< $line, bashपर शब्द बंटवारे, (हालांकि ग्लोबिंग नहीं) करता है $lineके रूप में यह वहाँ उद्धृत नहीं कर रहा है और उसके बाद जिसके परिणामस्वरूप अंतरिक्ष चरित्र के साथ शब्द जुड़ जाता है (डालता है एक अस्थायी फ़ाइल में एक नई पंक्ति चरित्र के द्वारा पीछा किया और और बनाता है कि की stdin cut)।

$ a=a,b,,c bash -c 'IFS=","; sed -n l <<< $a'
a b  c$

tabडिफ़ॉल्ट मान में होता है $IFS:

$ a=$'a\tb'  bash -c 'sed -n l <<< $a'
a b$

इसके साथ समाधान bashचर को उद्धृत करना है।

$ a=$'a\tb' bash -c 'sed -n l <<< "$a"'
a\tb$

ध्यान दें कि यह एकमात्र शेल है जो ऐसा करता है। zsh(जहां से <<<आता है, यूनिक्स पोर्ट से प्रेरित है rc) ksh93, mkshऔर yashजो भी समर्थन <<<करते हैं वह नहीं करते हैं।

यह सरणियों की बात आती है, mksh, yashऔर zshका पहला वर्ण के आधार पर संयोजन $IFS, bashऔर ksh93अंतरिक्ष पर।

$ mksh -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1:2$
$ yash -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1:2$
$ ksh -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1 2$
$ zsh -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1:2$
$ bash -c 'a=(1 2); IFS=:; sed -n l <<< "${a[@]}"'
1 2$

खाली होने पर zsh/ yashऔर mksh(संस्करण R52 कम से कम) के बीच अंतर होता $IFSहै:

$ mksh -c 'a=(1 2); IFS=; sed -n l <<< "${a[@]}"'
1 2$
$ zsh -c 'a=(1 2); IFS=; sed -n l <<< "${a[@]}"'
12$

आपके द्वारा उपयोग किए जाने पर गोले में व्यवहार अधिक सुसंगत होता है "${a[*]}"(सिवाय इसके कि mkshअभी भी एक बग है जब $IFSखाली है)।

में echo $line | ..., कि में हमेशा की तरह विभाजित + ग्लोब ऑपरेटर सब बॉर्न की तरह गोले लेकिन zsh(और हमेशा की तरह से जुड़ी समस्याओं echo)।


1
बहुत बढ़िया जवाब! धन्यवाद (+1)। मैं सबसे कम दोहराए जाने वाले प्रश्नकर्ता को स्वीकार करूंगा, क्योंकि उन्होंने मेरी मूर्खता को प्रकट करने के लिए सवाल का पूरी तरह से पर्याप्त जवाब दिया।
स्पार्कहॉक

10

समस्या यह है कि आप उद्धृत नहीं कर रहे हैं $line। जांच करने के लिए, दो लिपियों को बदलें ताकि वे बस प्रिंट करें $line:

#!/usr/bin/env bash
while read line; do
    echo $line
done < "$1"

तथा

#!/usr/bin/env zsh
while read line; do
    echo $line
done < "$1"

अब, उनके उत्पादन की तुलना करें:

$ bash.sh input 
foo bar baz
foo bar baz
$ zsh.sh input 
foo    bar    baz
foo    bar    baz

जैसा कि आप देख सकते हैं, क्योंकि आप उद्धृत नहीं कर रहे हैं $line, टैब को बैश द्वारा सही ढंग से व्याख्या नहीं किया गया है। Zsh के साथ बेहतर व्यवहार करने लगता है। अब, डिफ़ॉल्ट रूप से क्षेत्र परिसीमन के रूप में cutउपयोग करता है \t। इसलिए, चूंकि आपकी bashस्क्रिप्ट टैब (विभाजन + ग्लोब ऑपरेटर के कारण) खा रही है, cutकेवल एक फ़ील्ड देखता है और तदनुसार कार्य करता है। आप वास्तव में क्या चल रहे हैं:

$ echo "foo bar baz" | cut -f 2
foo bar baz

इसलिए, अपनी स्क्रिप्ट को दोनों गोले में अपेक्षित रूप से काम करने के लिए, अपने चर को उद्धृत करें:

while read line; do
    <<<"$line" cut -f 2
done < "$1"

फिर, दोनों एक ही आउटपुट का उत्पादन करते हैं:

$ bash.sh input 
bar
bar
$ zsh.sh input 
bar
bar

बहुत बढ़िया जवाब! धन्यवाद (+1)। मैं सबसे कम दोहराए जाने वाले प्रश्नकर्ता को स्वीकार करूंगा, क्योंकि उन्होंने मेरी मूर्खता को प्रकट करने के लिए सवाल का पूरी तरह से पर्याप्त जवाब दिया।
स्पार्कहॉक

^ वास्तव में सहीbash.sh
लॉयर

1

जैसा कि पहले ही उत्तर दिया जा चुका है, चर का उपयोग करने का एक अधिक पोर्टेबल तरीका इसे उद्धृत करना है:

$ printf '%s\t%s\t%s\n' foo bar baz
foo    bar    baz
$ l="$(printf '%s\t%s\t%s\n' foo bar baz)"
$ <<<$l     sed -n l
foo bar baz$

$ <<<"$l"   sed -n l
foo\tbar\tbaz$

लाइन के साथ बैश में कार्यान्वयन का अंतर है:

l="$(printf '%s\t%s\t%s\n' foo bar baz)"; <<<$l  sed -n l

यह अधिकांश गोले का परिणाम है:

/bin/sh         : foo bar baz$
/bin/b43sh      : foo bar baz$
/bin/bash       : foo bar baz$
/bin/b44sh      : foo\tbar\tbaz$
/bin/y2sh       : foo\tbar\tbaz$
/bin/ksh        : foo\tbar\tbaz$
/bin/ksh93      : foo\tbar\tbaz$
/bin/lksh       : foo\tbar\tbaz$
/bin/mksh       : foo\tbar\tbaz$
/bin/mksh-static: foo\tbar\tbaz$
/usr/bin/ksh    : foo\tbar\tbaz$
/bin/zsh        : foo\tbar\tbaz$
/bin/zsh4       : foo\tbar\tbaz$

केवल बैश को <<<अयोग्य करार दिए जाने पर दाईं ओर चर को विभाजित करें ।
हालाँकि, इसे bash संस्करण 4.4 पर सही किया गया है, जिसका
अर्थ है कि $IFSके परिणाम को प्रभावित करता है <<<


लाइन के साथ:

l=(1 2 3); IFS=:; sed -n l <<<"${l[*]}"

सभी गोले मूल्यों को जोड़ने के लिए IFS के पहले चरित्र का उपयोग करते हैं।

/bin/y2sh       : 1:2:3$
/bin/sh         : 1:2:3$
/bin/b43sh      : 1:2:3$
/bin/b44sh      : 1:2:3$
/bin/bash       : 1:2:3$
/bin/ksh        : 1:2:3$
/bin/ksh93      : 1:2:3$
/bin/lksh       : 1:2:3$
/bin/mksh       : 1:2:3$
/bin/zsh        : 1:2:3$
/bin/zsh4       : 1:2:3$

साथ "${l[@]}", एक जगह विभिन्न तर्कों को अलग करने की जरूरत है, लेकिन कुछ गोले आईएफएस से मान चुन (वह सही है?)।

/bin/y2sh       : 1:2:3$
/bin/sh         : 1 2 3$
/bin/b43sh      : 1 2 3$
/bin/b44sh      : 1 2 3$
/bin/bash       : 1 2 3$
/bin/ksh        : 1 2 3$
/bin/ksh93      : 1 2 3$
/bin/lksh       : 1:2:3$
/bin/mksh       : 1:2:3$
/bin/zsh        : 1:2:3$
/bin/zsh4       : 1:2:3$

एक अशक्त IFS के साथ, मान इस पंक्ति के साथ जुड़ने चाहिए:

a=(1 2 3); IFS=''; sed -n l <<<"${a[*]}"

/bin/y2sh       : 123$
/bin/sh         : 123$
/bin/b43sh      : 123$
/bin/b44sh      : 123$
/bin/bash       : 123$
/bin/ksh        : 123$
/bin/ksh93      : 123$
/bin/lksh       : 1 2 3$
/bin/mksh       : 1 2 3$
/bin/zsh        : 123$
/bin/zsh4       : 123$

लेकिन lksh और mksh दोनों ऐसा करने में विफल रहते हैं।

यदि हम तर्कों की सूची में बदल जाते हैं:

l=(1 2 3); IFS=''; sed -n l <<<"${l[@]}"

/bin/y2sh       : 123$
/bin/sh         : 1 2 3$
/bin/b43sh      : 1 2 3$
/bin/b44sh      : 1 2 3$
/bin/bash       : 1 2 3$
/bin/ksh        : 1 2 3$
/bin/ksh93      : 1 2 3$
/bin/lksh       : 1 2 3$
/bin/mksh       : 1 2 3$
/bin/zsh        : 123$
/bin/zsh4       : 123$

यश और zsh दोनों ही तर्क को अलग रखने में विफल हैं। क्या वह बग है?


के बारे में zsh/ yashऔर "${l[@]}"गैर-सूची संदर्भ में, यह डिजाइन द्वारा है जहां "${l[@]}"केवल सूची संदर्भों में विशेष है। गैर-सूची संदर्भों में, कोई अलगाव संभव नहीं है, आपको किसी तरह तत्वों को शामिल करने की आवश्यकता है। $ IFS के पहले चरित्र के साथ जुड़ना अंतरिक्ष चरित्र IMO के साथ जुड़ने की तुलना में अधिक सुसंगत है। dashयह भी करता है ( dash -c 'IFS=; a=$@; echo "$a"' x a b)। हालाँकि POSIX का इरादा उस IIRC को बदलने का है। इसे देखें (लंबी) चर्चा
स्टीफन चेज़लस


खुद को जवाब देते हुए, नहीं, दूसरा रूप होने पर, POSIX var=$@अनिर्दिष्ट व्यवहार के लिए छोड़ देगा ।
स्टीफन चेज़लस
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.