शेल: कमांड प्रतिस्थापन में नए सिरे ('\ n') को पीछे छोड़ते रहें


14

मैं एक कमांड प्रतिस्थापन के सटीक आउटपुट को कैप्चर करने में सक्षम होना चाहता हूं, जिसमें नई लाइन वर्ण शामिल हैं

मुझे पता है कि वे डिफ़ॉल्ट रूप से छीन लिए गए हैं, इसलिए उन्हें रखने के लिए कुछ हेरफेर की आवश्यकता हो सकती है, और मैं मूल निकास कोड रखना चाहता हूं

उदाहरण के लिए, नई अनुगामी और निकास कोड की एक चर संख्या के साथ एक कमांड दी गई है:

f(){ for i in $(seq "$((RANDOM % 3))"); do echo; done; return $((RANDOM % 256));}
export -f f

मैं कुछ इस तरह चलाना चाहता हूं:

exact_output f

और उत्पादन होना चाहिए:

Output: $'\n\n'
Exit: 5

मुझे दोनों bashऔर POSIX में दिलचस्पी है sh


1
न्यूलाइन का हिस्सा है $IFS, इसलिए इसे एक तर्क के रूप में कैप्चर नहीं किया जाएगा।
डेथग्रिप 16

4
@Deathgrip इससे कोई लेना-देना नहीं है IFS(कोशिश करें ( IFS=:; subst=$(printf 'x\n\n\n'); printf '%s' "$subst" )। केवल नई ख़बरें ही छीन ली जाएं। \t`` नहीं, और IFSइसे प्रभावित नहीं करता।
PSkocik



जवाबों:


17

POSIX गोले

सामान्य ( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ) एक कमांड का पूरा स्टडआउट प्राप्त करने के लिए चाल है:

output=$(cmd; ret=$?; echo .; exit "$ret")
ret=$?
output=${output%.}

विचार जोड़ना और अतिरिक्त करना है .\n। कमान प्रतिस्थापन केवल हटेगा कि \n । और तुम .साथ छीन लो ${output%.}

ध्यान दें कि इसके अलावा गोले में zsh, यह तब भी काम नहीं करेगा जब आउटपुट में NUL बाइट्स हों। साथ yash, कि अगर उत्पादन पाठ नहीं है काम नहीं करेगा।

यह भी ध्यान दें कि कुछ स्थानों में, यह मायने रखता है कि आप अंत में डालने के लिए किस वर्ण का उपयोग करते हैं। .आम तौर पर ठीक होना चाहिए, लेकिन कुछ अन्य नहीं हो सकता है। उदाहरण के लिए x(जैसा कि कुछ अन्य उत्तरों में इस्तेमाल किया गया है) @या BIG5, GB18030 या BIG5HKSCS वर्णक का उपयोग करके किसी लोकल में काम नहीं करेगा। उन वर्णमालाओं में, कई वर्णों का एन्कोडिंग एक ही बाइट में समाप्‍त होता है xया @(0x78, 0xX) के एन्कोडिंग के रूप में

उदाहरण के लिए, ūBIG5HKSCS में 0x88 0x78 है (और xASCII की तरह 0x78 है, सिस्टम पर सभी वर्णों में पोर्टेबल वर्ण सेट के सभी वर्णों के लिए समान एन्कोडिंग होनी चाहिए जिसमें अंग्रेजी अक्षर शामिल हैं, @और .)। अगर ऐसा cmdथा printf '\x88'और हम xइसके बाद सम्मिलित होते हैं, ${output%x}तो वह पट्टी करने में विफल होगा xजैसा $outputकि वास्तव में होगा ū

.इसके बजाय का उपयोग करने से सिद्धांत में एक ही समस्या हो सकती है यदि कोई वर्ण था, जिसका एन्कोडिंग उसी एन्कोडिंग के रूप में समाप्त होता है ., लेकिन कुछ समय पहले जाँच के लिए, मैं बता सकता हूं कि किसी भी वर्णमाला में एक लोकेल उपयोग के लिए उपलब्ध नहीं हो सकता है एक डेबियन, फ्रीबीएसडी या सोलारिस सिस्टम में ऐसे पात्र हैं जो मेरे लिए काफी अच्छे हैं (और क्यों मैं इस पर बस गया हूं .जो अंग्रेजी में किसी वाक्य के अंत को चिह्नित करने के लिए प्रतीक है, इसलिए उपयुक्त लगता है)।

@Arrow द्वारा चर्चित एक अधिक सही दृष्टिकोण केवल अंतिम चरित्र ( ${output%.}) के स्ट्रिपिंग के लिए लोकेल को C में बदलना होगा जो यह सुनिश्चित करेगा कि केवल एक बाइट छीन ली जाए, लेकिन यह कोड को महत्वपूर्ण बना देगा और संभावित रूप से अनुकूलता मुद्दों को लागू करेगा अपना ही है।

बैश / zsh विकल्प

साथ bashऔर zsh, यह मानते हुए उत्पादन नहीं NULs है, तो आप भी कर सकते हैं:

IFS= read -rd '' output < <(cmd)

के निकास स्थिति जानने के लिए cmd, आप कर सकते हैं wait "$!"; ret=$?में bashनहीं बल्कि में zsh

आर सी / es / akanaga

पूर्णता, ध्यान दें कि के लिए rc/ es/ akangaउस के लिए एक ऑपरेटर की है। उनमें, कमांड प्रतिस्थापन, `cmd(या `{cmd}अधिक जटिल आदेशों के लिए) के रूप में व्यक्त की गई सूची ( $ifsडिफ़ॉल्ट रूप से, स्पेस-टैब-न्यूलाइन को विभाजित करके) लौटाती है । उन गोले में (बॉर्न-जैसे गोले के विपरीत), न्यूलाइन की स्ट्रिपिंग केवल उस $ifsविभाजन के हिस्से के रूप में की जाती है । तो आप या तो खाली कर सकते हैं या $ifsउस ``(seps){cmd}फॉर्म का उपयोग कर सकते हैं जहाँ आप विभाजक निर्दिष्ट करते हैं:

ifs = ''; output = `cmd

या:

output = ``()cmd

किसी भी स्थिति में, कमांड की निकास स्थिति खो जाती है। आपको इसे आउटपुट में एम्बेड करना होगा और बाद में निकालना होगा जो बदसूरत हो जाएगा।

मछली

मछली में, कमांड प्रतिस्थापन (cmd)एक उपधारा के साथ है और इसमें शामिल नहीं है।

set var (cmd)

यदि खाली नहीं है, या एक से अधिक तक के छीनने के आउटपुट के साथ ( ज्यादातर अन्य गोले में सभी के विपरीत ) न्यूलाइन वर्ण $varके आउटपुट के साथ एक सरणी बनाता है यदि रिक्त है।cmd$IFScmd$IFS

तो इसमें अभी भी एक मुद्दा है (printf 'a\nb')और (printf 'a\nb\n')एक खाली चीज़ के साथ भी उसी चीज़ का विस्तार करना है $IFS

उस के आसपास काम करने के लिए, सबसे अच्छा मैं साथ आ सकता था:

function exact_output
  set -l IFS . # non-empty IFS
  set -l ret
  set -l lines (
    cmd
    set ret $status
    echo
  )
  set -g output ''
  set -l line
  test (count $lines) -le 1; or for line in $lines[1..-2]
    set output $output$line\n
  end
  set output $output$lines[-1]
  return $ret
end

एक विकल्प यह करना है:

read -z output < (begin; cmd; set ret $status; end | psub)

बॉर्न शेल

बॉर्न शेल ने न तो $(...)फॉर्म का समर्थन किया और न ही ${var%pattern}ऑपरेटर का, इसलिए इसे हासिल करना काफी कठिन हो सकता है। एक तरीका है eval और quoting का उपयोग करना:

eval "
  output='`
    exec 4>&1
    ret=\`
      exec 3>&1 >&4 4>&-
      (cmd 3>&-; echo \"\$?\" >&3; printf \"'\") |
        awk 3>&- -v RS=\\\\' -v ORS= -v b='\\\\\\\\' '
          NR > 1 {print RS b RS RS}; {print}; END {print RS}'
    \`
    echo \";ret=\$ret\"
  `"

यहाँ, हम एक पैदा कर रहे हैं

output='output of cmd
with the single quotes escaped as '\''
';ret=X

पास किया जाना eval। POSIX दृष्टिकोण के लिए, यदि 'उन वर्णों में से एक जिसका एन्कोडिंग अन्य वर्णों के अंत में पाया जा सकता है, तो हमें एक समस्या होगी (एक बहुत बुरा एक के रूप में यह कमांड इंजेक्शन भेद्यता बन जाएगा), लेकिन शुक्र है, जैसे ., यह उन लोगों में से एक नहीं है, और यह उद्धृत करने वाली तकनीक आम तौर पर वह है जो शेल कोड को उद्धृत करने वाली किसी भी चीज़ द्वारा उपयोग की जाती है (ध्यान दें कि \इसमें समस्या है, इसलिए इसका उपयोग नहीं किया जाना चाहिए (यह भी शामिल नहीं "..."है जिसके अंदर आपको कुछ वर्णों के लिए बैकस्लैश का उपयोग करने की आवश्यकता है) यहाँ, हम केवल इसका उपयोग कर रहे हैं, 'जो ठीक है)।

tcsh

Tcsh कमांड प्रतिस्थापन में नई सुर्खियाँ देखें `...`

(बाहर निकलने की स्थिति का ख्याल न रखना, जिसे आप इसे अस्थायी फ़ाइल में सहेज कर संबोधित कर सकते हैं ( echo $status > $tempfile:qकमांड के बाद))


धन्यवाद - और विशेष रूप से विभिन्न वर्णों पर सुराग के लिए। यदि एक चर में zshस्टोर NULकिया जा सकता है , तो IFS= read -rd '' output < <(cmd)काम क्यों नहीं करेगा ? इसे एक स्ट्रिंग की लंबाई को स्टोर करने में सक्षम होने की आवश्यकता है ... क्या यह ''0-बाइट स्ट्रिंग के \0बजाय 1-बाइट स्ट्रिंग के रूप में एन्कोड करता है ?
टॉम हेल

1
@TomHale, हाँ, read -d ''के रूप में इलाज किया जाता है read -d $'\0'( bashसाथ ही हालांकि हर जगह $'\0'समान ''है)।
स्टीफन चेज़लस

आप पात्रों और बाइट्स का सामना कर रहे हैं। कृपया यह समझें कि यदि हम वास्तव में जो जोड़ा गया है, उसे हटा दें, तो मूल इकाई को नहीं बदलना चाहिए। ऐसा नहीं है कि एक बाइट को हटाने के लिए अलग-अलग कहा जाता xहै कि क्या जोड़ा गया था। कृपया मेरे संपादित उत्तर पर एक नज़र डालें।
आइजैक

@ एरो, हाँ var=value command evalट्रिक की चर्चा यहां ( भी ) और ऑस्ट्रिन -ग्रुप मेलिंग लिस्ट से पहले की गई थी। आप पाएंगे कि यह पोर्टेबल नहीं है (और यह काफी स्पष्ट है जब आप चीजों को पसंद कर रहे हैं a=1 command eval 'unset a; a=2'या इससे भी बदतर है कि यह उस तरह का उपयोग करने के लिए नहीं था)। उसी के लिए वही savedVAR=$VAR;...;VAR=$savedVARकरता है जो आप तब चाहते हैं जब $VARशुरू में परेशान थे । यदि यह केवल एक सैद्धांतिक समस्या के आसपास काम करने के लिए है (एक बग जो व्यवहार में नहीं मारा जा सकता है), IMO, यह परेशान करने लायक नहीं है। फिर भी, मैं कोशिश करने के लिए आपका समर्थन करूंगा।
स्टीफन चेजेलस

क्या आपके पास एक लिंक है जहां आपने डिस्कनेक्ट किया है और अंत में LANG=Cएक स्ट्रिंग से एक बाइट को हटाने के उपयोग को त्याग दिया है ? आप वास्तविक बिंदु के आसपास चिंताएं बढ़ा रहे हैं, सभी को हल करना आसान है। (1) वहाँ कोई प्रयोग नहीं किया जाता है (2) परिवर्तन करने से पहले चर का परीक्षण करें। @ स्टीफनचेज़ेलस
आइजैक

3

नए प्रश्न के लिए, यह स्क्रिप्ट काम करती है:

#!/bin/bash

f()           { for i in $(seq "$((RANDOM % 3 ))"); do
                    echo;
                done; return $((RANDOM % 256));
              }

exact_output(){ out=$( $1; ret=$?; echo x; exit "$ret" );
                unset OldLC_ALL ; [ "${LC_ALL+set}" ] && OldLC_ALL=$LC_ALL
                LC_ALL=C ; out=${out%x};
                unset LC_ALL ; [ "${OldLC_ALL+set}" ] && LC_ALL=$OldLC_ALL
                 printf 'Output:%10q\nExit :%2s\n' "${out}" "$?"
               }

exact_output f
echo Done

निष्पादन पर:

Output:$'\n\n\n'
Exit :25
Done

लंबा विवरण

POSIX गोले को हटाने से निपटने के लिए सामान्य ज्ञान \nहै:

जोड़ें x

s=$(printf "%s" "${1}x"); s=${s%?}

इसकी आवश्यकता है क्योंकि पिछले नई लाइन ( एस ) को POSIX विनिर्देश प्रति कमांड विस्तार द्वारा हटा दिया गया है :

प्रतिस्थापन के अंत में एक या अधिक वर्णों के अनुक्रमों को हटाना।


एक अनुगामी के बारे में x

इस प्रश्न में कहा गया है कि xकुछ एन्कोडिंग में कुछ चरित्र के अनुगामी बाइट के साथ भ्रमित किया जा सकता है। लेकिन हम यह अनुमान लगाने जा रहे हैं कि कुछ संभावित एन्कोडिंग में किसी भाषा में कौन सा या कौन सा चरित्र बेहतर है, जो कि कम से कम कहने के लिए एक कठिन प्रस्ताव है।

हालाँकि; वह बस गलत है

एकमात्र नियम जिसे हमें अनुसरण करने की आवश्यकता है वह है ठीक उसी तरह जो हम हटाते हैं।

यह समझना आसान होना चाहिए कि यदि हम किसी मौजूदा स्ट्रिंग (या बाइट अनुक्रम) में कुछ जोड़ते हैं और बाद में हम उसी चीज़ को बिल्कुल हटा देते हैं, तो मूल स्ट्रिंग (या बाइट अनुक्रम) समान होना चाहिए।

हम कहां गलत हैं? जब हम पात्रों और बाइट्स को मिलाते हैं

यदि हम एक बाइट जोड़ते हैं, तो हमें एक बाइट को हटाना होगा, यदि हम एक चरित्र को जोड़ते हैं तो हमें ठीक उसी वर्ण को निकालना होगा ।

दूसरा विकल्प, एक चरित्र को जोड़ना (और बाद में सटीक समान चरित्र को हटाने) जटिल और जटिल हो सकता है, और, हाँ, कोड पृष्ठ और एन्कोडिंग रास्ते में मिल सकते हैं।

हालांकि, पहला विकल्प काफी संभव है, और, यह समझाने के बाद, यह सरल सरल हो जाएगा।

आइए एक बाइट, एक ASCII बाइट (<127) को जोड़ते हैं, और चीजों को जितना संभव हो उतना कम दोषपूर्ण रखने के लिए, आइए az की श्रेणी में एक ASCII वर्ण कहते हैं। या जैसा कि हमें यह कहना चाहिए, हेक्स रेंज में एक बाइट 0x61- 0x7a। चलो उनमें से किसी को चुनते हैं, शायद एक एक्स (वास्तव में मूल्य का एक बाइट 0x78)। हम एक एक्स को एक स्ट्रिंग में परिवर्तित करके इस तरह के बाइट को जोड़ सकते हैं (मान लेते हैं é):

$ a
$ b=${a}x

यदि हम स्ट्रिंग को बाइट्स के अनुक्रम के रूप में देखते हैं, तो हम देखते हैं:

$ printf '%s' "$b" | od -vAn -tx1c
  c3  a9  78
 303 251   x

एक स्ट्रिंग अनुक्रम जो एक एक्स में समाप्त होता है।

यदि हम उस x (बाइट मान 0x78) को हटा देते हैं, तो हमें यह मिलता है:

$ printf '%s' "${b%x}" | od -vAn -tx1c
  c3  a9
 303 251

यह बिना किसी समस्या के काम करता है।

थोड़ा और मुश्किल उदाहरण।

आओ हम कहते हैं कि बाइट में हम जिस स्ट्रिंग में रुचि रखते हैं 0xc3:

$ a=$'\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x20\xc3'

और मान के बाइट को जोड़ने देता है 0xa9

$ b=$a$'\xa9'

स्ट्रिंग अब यह बन गया है:

$ echo "$b"
a test string é

वास्तव में मैं जो चाहता था, अंतिम दो बाइट्स utf8 में एक चरित्र हैं (इसलिए कोई भी अपने utf8 कंसोल में इस परिणाम को पुन: पेश कर सकता है)।

यदि हम एक चरित्र को हटाते हैं, तो मूल स्ट्रिंग को बदल दिया जाएगा। लेकिन यह वह नहीं है जो हमने जोड़ा था, हमने एक बाइट मान जोड़ा, जो कि एक एक्स के रूप में लिखा जाना होता है, लेकिन वैसे भी एक बाइट।

पात्रों के रूप में गलत बाइट से बचने के लिए हमें क्या करने की आवश्यकता है। हमें जो कुछ चाहिए वह एक ऐसी क्रिया है जो हमारे द्वारा उपयोग की जाने वाली बाइट को हटा देती है 0xa9। वास्तव में, राख, बाश, लक्स और मक्ष सब कुछ ठीक करने लगते हैं:

$ c=$'\xa9'
$ echo ${b%$c} | od -vAn -tx1c
 61  20  74  65  73  74  20  73  74  72  69  6e  67  20  c3  0a
  a       t   e   s   t       s   t   r   i   n   g     303  \n

लेकिन ksh या zsh नहीं।

हालाँकि, यह हल करना बहुत आसान है, उन सभी गोले को बाइट हटाने के लिए बताता है :

$ LC_ALL=C; echo ${b%$c} | od -vAn -tx1c 

यह बात है, सभी गोले परीक्षण किए गए कार्य (यश को छोड़कर) (स्ट्रिंग के अंतिम भाग के लिए):

ash             :    s   t   r   i   n   g     303  \n
dash            :    s   t   r   i   n   g     303  \n
zsh/sh          :    s   t   r   i   n   g     303  \n
b203sh          :    s   t   r   i   n   g     303  \n
b204sh          :    s   t   r   i   n   g     303  \n
b205sh          :    s   t   r   i   n   g     303  \n
b30sh           :    s   t   r   i   n   g     303  \n
b32sh           :    s   t   r   i   n   g     303  \n
b41sh           :    s   t   r   i   n   g     303  \n
b42sh           :    s   t   r   i   n   g     303  \n
b43sh           :    s   t   r   i   n   g     303  \n
b44sh           :    s   t   r   i   n   g     303  \n
lksh            :    s   t   r   i   n   g     303  \n
mksh            :    s   t   r   i   n   g     303  \n
ksh93           :    s   t   r   i   n   g     303  \n
attsh           :    s   t   r   i   n   g     303  \n
zsh/ksh         :    s   t   r   i   n   g     303  \n
zsh             :    s   t   r   i   n   g     303  \n

बस इतना आसान है, एक LC_ALL = C वर्ण को हटाने के लिए शेल को बताएं, जो कि सभी बाइट मानों के लिए बिल्कुल एक बाइट 0x00है 0xff

टिप्पणियों के लिए समाधान:

टिप्पणियों में चर्चा किए गए उदाहरण के लिए, एक संभावित समाधान (जो zsh में विफल रहता है) है:

#!/bin/bash

LC_ALL=zh_HK.big5hkscs

a=$(printf '\210\170');
b=$(printf '\170');

unset OldLC_ALL ; [ "${LC_ALL+set}" ] && OldLC_ALL=$LC_ALL
LC_ALL=C ; a=${a%"$b"};
unset LC_ALL ; [ "${OldLC_ALL+set}" ] && LC_ALL=$OldLC_ALL

printf '%s' "$a" | od -vAn -c

जो एन्कोडिंग की समस्या को दूर करेगा।


यह जानकर अच्छा लगा कि एक से अधिक ट्रेलिंग न्यूलाइन को हटाया जा सकता है।
टॉम हेल


मैं मानता हूं कि यह सुनिश्चित करने के लिए सी को लोकेल तय करना कि ${var%?}हमेशा स्ट्रिप्स एक बाइट सिद्धांत में अधिक सही है, लेकिन: 1- LC_ALLऔर LC_CTYPEओवरराइड $LANG, इसलिए आपको सेट करने की आवश्यकता होगी LC_ALL=C2- आप var=${var%?}बदलाव के रूप में एक उपधारा में नहीं कर सकते। खो दिया है, तो आप को बचाने के लिए और LC_ALL(या गैर- POSIX localगुंजाइश सुविधाओं का सहारा ) के मूल्य और स्थिति को बहाल करने की आवश्यकता होगी 3- स्क्रिप्ट के माध्यम से लोकल मिडवे को बदलना यश जैसे कुछ गोले में पूरी तरह से समर्थित नहीं है। दूसरे छोर पर, अभ्यास .वास्तविक जीवन के चार्ट में कभी भी समस्या नहीं है, इसलिए इसका उपयोग LC_ALL के साथ मेलिंग से बचा जाता है।
स्टीफन चेज़लस

2

आप सामान्य आउटपुट के बाद एक चरित्र का उत्पादन कर सकते हैं और फिर उसे उतार सकते हैं:

#capture the output of "$@" (arguments run as a command)
#into the exact_output` variable
exact_output() 
{
    exact_output=$( "$@" && printf X ) && 
    exact_output=${exact_output%X}
}

यह एक POSIX अनुरूप समाधान है।


प्रतिक्रियाओं के आधार पर, मुझे लगता है कि मेरा प्रश्न अस्पष्ट था। मैंने अभी इसे अपडेट किया है।
टॉम हेल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.