कैसे एक स्ट्रिंग चर में लाइनों की संख्या की गणना करने के लिए POSIX-ly?


10

मुझे पता है कि मैं यह बाश में कर सकता हूं:

wc -l <<< "${string_variable}"

मूल रूप से, मैंने जो कुछ भी पाया उसमें <<<बैश ऑपरेटर शामिल था ।

लेकिन POSIX शेल में, <<<अपरिभाषित है, और मैं घंटों तक एक वैकल्पिक दृष्टिकोण खोजने में असमर्थ रहा हूं। मुझे पूरा यकीन है कि इसका एक सरल समाधान है, लेकिन दुर्भाग्य से, मुझे यह अब तक नहीं मिला।

जवाबों:


11

इसका सरल उत्तर यह है कि wc -l <<< "${string_variable}"ksh / bash / zsh शॉर्टकट है printf "%s\n" "${string_variable}" | wc -l

रास्ते <<<और पाइप के काम में वास्तव में अंतर हैं: <<<एक अस्थायी फ़ाइल बनाता है जिसे कमांड के इनपुट के रूप में पारित किया जाता है, जबकि |एक पाइप बनाता है। बैश और pdksh / mksh में (लेकिन ksh93 या zsh में नहीं), पाइप के दाईं ओर की कमान एक उप-प्रकार में चलती है। लेकिन इस विशेष मामले में ये अंतर मायने नहीं रखते हैं।

ध्यान दें कि गिनती लाइनों के संदर्भ में, यह मानता है कि चर खाली नहीं है और एक नई रेखा के साथ समाप्त नहीं होता है। एक न्यूलाइन के साथ समाप्त नहीं होता है यह मामला है जब चर एक कमांड प्रतिस्थापन का परिणाम है, इसलिए आपको अधिकांश मामलों में सही परिणाम मिलेगा, लेकिन आपको खाली स्ट्रिंग के लिए 1 मिलेगा।

वहाँ दोनों के बीच मतभेद हैं var=$(somecommand); wc -l <<<"$var"और somecommand | wc -l: एक कमांड प्रतिस्थापन का उपयोग कर और एक अस्थायी चर दूर अंत में रिक्त लाइनों, भूल है कि क्या उत्पादन की अंतिम पंक्ति या नहीं एक नई पंक्ति में समाप्त हो गया स्ट्रिप्स (यह हमेशा आदेश एक वैध अरिक्त पाठ फ़ाइल आउटपुट अगर करता है) , और अगर उत्पादन खाली है तो एक-एक करके ओवरकॉन्‍ट करता है। यदि आप परिणाम को संरक्षित करना चाहते हैं और लाइनों को गिनना चाहते हैं, तो आप इसे किसी ज्ञात पाठ को जोड़कर और इसे अंत में अलग कर सकते हैं:

output=$(somecommand; echo .)
line_count=$(($(printf "%s\n" "$output" | wc -l) - 1))
printf "The exact output is:\n%s" "${output%.}"

1
@ इनियन कीपिंग wc -lमूल के बिल्कुल समतुल्य है: (भले ही खाली हो) <<<$fooके मूल्य में एक नई रेखा जोड़ता है । मैं अपने जवाब में समझाता हूं कि ऐसा क्यों नहीं हो सकता था जो वह चाहते थे, लेकिन यह वही है जो पूछा गया था। $foo$foo
गिल्स का SO- '

2

शेल-निर्मित के अनुरूप नहीं, बाहरी उपयोगिताओं जैसे कि grepऔर awkपोसिक्स के अनुरूप विकल्पों का उपयोग करके ,

string_variable="one
two
three
four"

grepलाइनों की शुरुआत मैच के साथ करना

printf '%s' "${string_variable}" | grep -c '^'
4

और साथ awk

printf '%s' "${string_variable}" | awk 'BEGIN { count=0 } NF { count++ } END { print count }'

ध्यान दें कि कुछ GNU टूल, विशेष रूप से, GNU टूल के POSIX संस्करण को चलाने के लिए विकल्प का grepसम्मान नहीं POSIXLY_CORRECT=1करता है। में grepकेवल व्यवहार चर की स्थापना से प्रभावित कमांड लाइन झंडे के आदेश के प्रसंस्करण में अंतर हो जाएगा। प्रलेखन (GNU grepमैनुअल) से, ऐसा लगता है

POSIXLY_CORRECT

यदि सेट किया गया है, तो Grep POSIX की आवश्यकता के अनुसार व्यवहार करता है; अन्यथा, grepअन्य GNU कार्यक्रमों की तरह अधिक व्यवहार करता है। POSIX के लिए आवश्यक है कि फ़ाइल नामों का पालन करने वाले विकल्पों को फ़ाइल नामों के रूप में माना जाए; डिफ़ॉल्ट रूप से, इस तरह के विकल्पों को ऑपरेंड सूची के सामने अनुमति दी जाती है और विकल्प के रूप में माना जाता है।

देखें कि grep में POSIXLY_CORRECT का उपयोग कैसे करें?


2
निश्चित रूप wc -lसे अभी भी यहाँ व्यवहार्य है?
माइकल होमर

@ मिचेलहोमर: मैंने जो देखा है, उसमें wc -lएक उचित न्यूलाइन सीमांकित स्ट्रीम की आवश्यकता है (ठीक से गिनने के लिए अंत में एक अनुगामी '\ n')। एक के साथ प्रयोग करने के लिए एक साधारण फीफो का उपयोग नहीं किया जा सकता है printf, उदाहरण के लिए printf '%s' "${string_variable}" | wc -lउम्मीद के <<<मुताबिक काम नहीं कर सकते हैं , लेकिन \nहेरिंग द्वारा संलग्न अनुगामी के कारण
Inian

1
इससे printf '%s\n'पहले कि आप इसे बाहर निकालते, वह क्या कर रहा था ...
माइकल होमर

1

यहाँ स्ट्रिंग <<<बहुत अधिक यहाँ दस्तावेज़ का एक-लाइन संस्करण है <<। पूर्व एक मानक विशेषता नहीं है, लेकिन बाद वाला है। आप <<इस मामले में भी उपयोग कर सकते हैं । ये समतुल्य होने चाहिए:

wc -l <<< "$somevar"

wc -l << EOF
$somevar
EOF

हालाँकि, ध्यान दें कि दोनों एक अतिरिक्त नई पंक्ति जोड़ते हैं $somevar, उदाहरण के लिए यह प्रिंट 6, भले ही चर में केवल पाँच लाइनें हों:

s=$'foo\n\n\nbar\n\n'
wc -l <<< "$s"

इसके साथ printf, आप यह तय कर सकते हैं कि आपको अतिरिक्त न्यूलाइन चाहिए या नहीं:

printf "%s\n" "$s" | wc -l         # 6
printf "%s"   "$s" | wc -l         # 5

लेकिन फिर, ध्यान दें कि wcकेवल पूर्ण पंक्तियों (या स्ट्रिंग में न्यूलाइन वर्णों की संख्या) को गिना जाता है। grep -c ^अंतिम पंक्ति के टुकड़े को भी गिनना चाहिए।

s='foo'
printf "%s" "$s" | wc -l           # 0 !

printf "%s" "$s" | grep -c ^       # 1

(बेशक आप ${var%...}लूप में एक बार में उन्हें हटाने के लिए विस्तार का उपयोग करके पूरी तरह से शेल में लाइनों की गणना कर सकते हैं ...)


0

उन आश्चर्यजनक रूप से लगातार मामलों में जहां आपको वास्तव में क्या करने की आवश्यकता है, सभी गैर-रिक्त लाइनों को कुछ फ़ैशन में एक चर के अंदर संसाधित करना है (उन्हें गिनना भी शामिल है), आप IFS को केवल एक नई रेखा पर सेट कर सकते हैं और फिर शेल के शब्द विभाजन तंत्र का उपयोग कर सकते हैं गैर-खाली लाइनों के अलावा।

उदाहरण के लिए, यहां एक छोटा शेल फ़ंक्शन है जो सभी आपूर्ति किए गए तर्कों के अंदर गैर-खाली लाइनों को योग करता है:

lines() (
IFS='
'
set -f #disable pathname expansion
set -- $*
echo $#
)

कोष्ठक के बजाय कोष्ठक का उपयोग यहां फ़ंक्शन बॉडी के लिए कंपाउंड कमांड बनाने के लिए किया जाता है। यह फ़ंक्शन को एक सब-टाइम में निष्पादित करता है ताकि यह हर कॉल पर बाहरी दुनिया के IFS चर और pathname विस्तार सेटिंग को प्रदूषित न करे।

यदि आप गैर-खाली लाइनों पर पुनरावृति करना चाहते हैं, तो आप इसे इसी तरह कर सकते हैं:

IFS='
'
set -f
for line in $lines
do
    printf '[%s]\n' $line
done

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

उदाहरण के लिए, यदि आप किसी चीज़ के लिए एक जटिल कमांड लाइन बनाने के लिए चर का उपयोग कर रहे हैं, तो आप केवल तभी ffmpegशामिल करना चाह सकते हैं -vf scale=$scaleजब चर scaleको कुछ गैर-रिक्त पर सेट किया जाए। आम तौर पर आप के साथ इस लक्ष्य को हासिल कर सकता है ${scale:+-vf scale=$scale}, लेकिन अगर आईएफएस समय इस पैरामीटर विस्तार किया जाता है पर अपने सामान्य अंतरिक्ष चरित्र शामिल नहीं है, अंतरिक्ष के बीच -vfऔर scale=एक शब्द विभाजक के रूप में उपयोग नहीं किया जाएगा और ffmpegसभी के पारित हो जाएगा -vf scale=$scaleएक भी तर्क के रूप में, जिसे यह समझ नहीं आएगा।

इसे ठीक करने के लिए, आपको यह सुनिश्चित करने की आवश्यकता होगी कि ${scale}विस्तार करने से पहले IFS को सामान्य रूप से सेट किया गया था , या दो विस्तार करें ${scale:+-vf} ${scale:+scale=$scale}:। शब्द को विभाजित करना जो शेल कमांड लाइनों के प्रारंभिक पार्सिंग की प्रक्रिया में करता है, जैसा कि उन कमांड लाइनों के प्रसंस्करण के विस्तार चरण के दौरान विभाजन के विपरीत होता है, IFS पर निर्भर नहीं करता है।

कुछ और जो आपके लायक हो सकता है अगर आप इस तरह का काम करने जा रहे हैं तो सिर्फ एक टैब और एक नई लाइन रखने के लिए दो वैश्विक शेल वैरिएबल बनाए जाएंगे:

t=' '
n='
'

इस तरह आप बस शामिल कर सकते हैं $tऔर $nविस्तार जहां टैब और नई-पंक्तियों की जरूरत है, न कि कचरा से उद्धृत सफेद स्थान के साथ अपने सभी कोड में। यदि आप इसके बजाय POSIX खोल में पूरी तरह से उद्धृत व्हाट्सएप से बचते हैं, जिसमें ऐसा करने के लिए कोई अन्य तंत्र नहीं है, तो printfमदद कर सकता है, हालांकि आपको कमांड विस्तारों में नई रूपरेखाओं को हटाने के लिए काम करने के लिए थोड़ी सी फ़िदालिंग की आवश्यकता है:

nt=$(printf '\n\t')
n=${nt%?}
t=${nt#?}

कभी-कभी IFS की स्थापना करना जैसे कि यह एक प्रति-कमांड वातावरण चर अच्छा काम करता है। उदाहरण के लिए, यहाँ एक लूप है जो एक पथनाम को पढ़ता है जिसमें रिक्त स्थान शामिल है और टैब-सीमांकित इनपुट फ़ाइल की प्रत्येक पंक्ति से स्केलिंग कारक है:

while IFS=$t read -r path scale
do
    ffmpeg -i "$path" ${scale:+-vf scale=$scale} "${path%.*}.out.mkv"
done <recode-queue.txt

इस मामले में readबिलिन IFS को सिर्फ एक टैब पर सेट करता है, इसलिए यह रिक्त स्थान पर पढ़ी गई इनपुट लाइन को विभाजित नहीं करेगा। लेकिन IFS=$t set -- $lines काम नहीं करता है: शेल का विस्तार होता है $linesक्योंकि यह कमांड निष्पादित करने से पहलेset बिलिन के तर्कों का निर्माण करता है , इसलिए आईएफएस की अस्थायी सेटिंग इस तरह से होती है जो केवल बिलिन के निष्पादन के दौरान ही लागू होती है। यही कारण है कि कोड स्निपेट मैंने सभी सेट IFS के ऊपर एक अलग चरण में दिए हैं, और उन्हें इसे संरक्षित करने के मुद्दे से क्यों निपटना है।

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