3 पॉइंट द्वारा GN⁺ 2023-11-24 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • Unix जैसे सिस्टमों में /bin/[ नाम की एक executable file हो सकती है, जिसका नाम सिर्फ एक अक्षर वाला symbol है, और जो syntax shell conditional expression जैसा दिखता है वह असल में command execution और exit code पर आधारित होता है
  • test expression को evaluate करता है और true होने पर 0, false होने पर 1 लौटाता है; जब इसे [ के रूप में call किया जाता है, तो यह यह भी check करता है कि आखिरी argument ] है या नहीं
  • कई shells test और [ को built-in command के रूप में भी देते हैं, इसलिए external /bin/test और shell built-in implementation के error messages या behavior अलग हो सकते हैं
  • Bash extension [[ external command नहीं, बल्कि built-in syntax है; इसलिए यह उदाहरण की तरह long* को glob-expand किए बिना literal string के रूप में compare करता है और [ से अलग rules लागू करता है
  • Portable scripts के लिए [ सही है, और अगर script सिर्फ Bash के लिए है तो लगातार [[ इस्तेमाल करना बेहतर है, लेकिन चुनाव करते समय दोनों तरीकों के expansion rules का फर्क समझना चाहिए

/bin/[ और /bin/test की असलियत

  • Unix systems में /bin/[ नाम की executable file हो सकती है, जिसका नाम सिर्फ एक अक्षर वाला symbol है
    • उदाहरण command ls /bin/? /bin/[ दिखाता है
  • /bin/[ और /bin/test एक ही binary की ओर point कर सकते हैं
    • उदाहरण में दोनों paths एक ही inode और size वाली files के रूप में दिखते हैं
    • हालांकि हर system में उनका hard link होना जरूरी नहीं है
  • test shell में expressions evaluate करने वाला program है
    • String comparison
    • Numeric comparison
    • File conditions check करना
  • Evaluation result true होने पर exit code 0, false होने पर 1 लौटाता है

[ command की तरह क्यों काम करता है

  • test a = b को conditional expression जैसा समझना मुश्किल है, लेकिन वही logic [ a = b ] की तरह लिखने पर ज्यादा familiar रूप मिलता है
  • [ अलग syntax जैसा दिखता है, लेकिन असल में यह command call है
    • if [ a = b ]; then ... fi [ command चलाता है और exit code check करता है
    • [ के रूप में call होने पर program check करता है कि आखिरी argument closing square bracket ] है या नहीं
  • if statement conditional expression को सीधे interpret करने के बजाय, दिए गए command के exit code के आधार पर branch करता है
    • test a = a; echo $? 0 है
    • test a = b; echo $? 1 है
    • [ a = a ]; echo $? 0 है
    • [ a = b ]; echo $? 1 है
  • इसी context में true और false को भी exit code लौटाने वाली helper binaries माना जा सकता है

External binary और shell built-in commands का फर्क

  • test और [ shell scripts में अक्सर इस्तेमाल होते हैं, इसलिए ज्यादातर shells इन्हें built-in command के रूप में भी implement करते हैं
  • एक ही input पर भी external binary और shell built-in command का output अलग हो सकता है
    • /bin/test a b test: a: unexpected operator देता है
    • test a b dash: 2: test: a: unexpected operator देता है
  • ऐसा फर्क सिर्फ test और [ में नहीं, बल्कि echo जैसे सरल दिखने वाले commands में भी हो सकता है
  • हर shell का built-in implementation अलग होता है, इसलिए script का behavior चलाने वाले shell के हिसाब से बदल सकता है

Bash extension [[ द्वारा लागू अलग rules

  • [[ एक Bash extension है और [ के इस्तेमाल की जगह ले सकता है
  • सबसे बड़ा फर्क यह है कि [[ हमेशा built-in syntax होता है
    • [ external binary के रूप में चल सकता है, लेकिन [[ में Bash expression के अंदर की language rules बदल सकता है
  • Glob example में [ और [[ अलग तरह से काम करते हैं
    • touch long-name के बाद [ long* = long-name ] && echo match match print करता है
    • [ command के arguments पर सामान्य shell expansion rules लागू होते हैं, इसलिए long* directory के long-name में expand हो जाता है
    • [[ long* = long-name ]] && echo match कोई output नहीं देता
    • [[ long* को literal string की तरह treat करता है और उसे सीधे long-name से compare करता है, इसलिए fail होता है
  • अगर script सिर्फ Bash के लिए है, तो [[ के साथ regular expression matching =~ जैसे features भी इस्तेमाल किए जा सकते हैं

Scripts में क्या चुनें

  • Portable shell scripts के लिए [ इस्तेमाल करना सही रहता है
  • test भी इस्तेमाल किया जा सकता है, लेकिन यह common choice नहीं है
  • अगर script सिर्फ Bash के लिए है, तो लगातार [[ इस्तेमाल करना बेहतर है
  • Shell में खुद भी !, &&, || जैसे expression operators होते हैं
    • ये operators command के exit status के आधार पर काम करते हैं
    • grep ^hello$ ... && grep ^bye$ ... में दोनों commands सफल होने पर overall exit code 0 होता है
    • अगर पहला grep fail हो जाए, तो && के बाद वाला command सफल नहीं होता और overall exit code 1 होता है
  • इसलिए test/[ expressions और shell logical operators को एक ही conditional statement में combine किया जा सकता है
    • उदाहरण: [ a = b ] || grep -q ^hello$ /usr/share/dict/words
  • POSIX यह require नहीं करता कि /bin/[ और /bin/test hard links हों
    • NetBSD में वे hard links थे
    • macOS Catalina एक ही binary की अलग copies देता है
    • Debian testing अलग-अलग binaries देता है
    • POSIX specification यह require नहीं करता कि दोनों files linked हों

1 टिप्पणियां

 
GN⁺ 2023-11-24
Hacker News की राय
  • मूल लेखक मैं ही हूँ। साझा करने के लिए धन्यवाद, और खुशी है कि यह front page तक पहुँच गया। शीर्षक में शायद (2020) लगना सही होगा, और "test" असल में command को संदर्भित करता है, इसलिए इसे capitalized न करना बेहतर होगा
    2021 में लिखा एक संबंधित लेख भी है, जिसमें bash के [[ operator तक को कवर किया गया है, इसलिए इस context में यह दिलचस्प लग सकता है: https://jmmv.dev/2021/08/useless-use-of-gnu.html

    • सख्ती से कहें तो [[ built-in command नहीं, बल्कि मूल रूप से syntax element के ज्यादा करीब है। शायद internally यह किसी लगभग inaccessible built-in command का उपयोग करता होगा, लेकिन दिलचस्प बात यह है कि ]] भी reserved word है, भले ही वह ऐसी जगह नहीं आ सकता जहाँ reserved word का अर्थ हो
      कुछ non-bash shells में खास तरह के functions declare करने के लिए function keyword जरूरी होता है। make का $(shell) कई targets build करते समय performance में measurable फर्क ला सकता है। फिर भी जब कुछ भी नहीं करना हो तो यह नुकसान है, इसलिए आम तौर पर regeneration trigger करने के लिए include इस्तेमाल करना सही रहता है। GNU का POSIX को ignore करना पूरी तरह वाजिब है, क्योंकि POSIX ज्यादातर वास्तविक समस्याएँ हल करने में बहुत उपयोगी नहीं है
    • https://jmmv.dev/2021/08/useless-use-of-gnu.html में चर्चा किए गए GNU extensions में से कई interactive use में बहुत उपयोगी हैं। current directory को explicit . के बिना search करना भी उपयोगी है, और अभी-अभी typed command में बाद में options जोड़ देना भी सचमुच convenient है। ऐसे commands देखकर हमेशा झुंझलाहट होती है जो यह support नहीं करते
      scripts में POSIX sh के अनुसार रहना आम तौर पर reasonable है। कम-से-कम यह पता होना चाहिए कि आप Bash-only syntax इस्तेमाल कर रहे हैं या नहीं
    • वह लेख शिकायत करता है कि --ignore-case, set -o pipefail जैसे GNU extensions इस्तेमाल करने से scripts की portability घटती है। यह अपने-आप में सही है
      लेकिन यह नहीं बताता कि Linux users को portability की इतनी चिंता क्यों करनी चाहिए। OpenBSD और FreeBSD ठीक-ठाक चल रहे हैं, लेकिन उनके users इतने कम हैं कि वे खास चिंता का विषय नहीं लगते। निष्पक्षता के लिहाज से कहा जा सकता है कि ऐसे operating systems को भी consider करना चाहिए, लेकिन यह criterion कहाँ जाकर रुकेगा? क्या vxWorks जैसी obscure चीजें भी consider करनी होंगी? BusyBox, Alpine वाला पक्ष ज्यादा दिलचस्प है, लेकिन changes इतने बड़े होते हैं कि लगभग हमेशा अलग porting करनी ही पड़ती है। GNU के अलावा दूसरे ecosystems की चिंता करने का कोई और convincing कारण है क्या?
    • if [ a = b ] || grep -q ^hello$ /usr/share/dict/words; then echo "test failed and grep succeeded"; fi जैसी चीज तो बस साधारण रोजमर्रा की shell usage नहीं है क्या?
    • एक side note: लगता है blog software ने title बिगाड़ दिया। उदाहरण के लिए यह make $(shell …) expansion की तरह दिया गया है, जबकि मूल रूप से make $(shell ...) expansion होना चाहिए
      body में जैसे सही लिखा है, यह तीन dots हैं, एक ellipsis नहीं, इसलिए mldr खुद भी सही नहीं है। शायद दो unrelated bugs ने एक साथ असर डाला है
  • आखिरी point को एक कदम और आगे बढ़ाएँ तो if block खुद भी हटाया जा सकता है। if [ a = b ]; then echo "Oops!"; else echo "Expected; phew!"; fi बन जाता है [ a = b ] && echo "Oops!" || echo "Expected; phew!"
    यह कितनी बार करना चाहिए, पता नहीं, लेकिन [ "$debug" ] && echo "what's going on" >&2 की तरह conditional debug output को standard error पर print करते समय कभी-कभी उपयोगी होता है। इस तथ्य की वजह से कि if block सामान्य command को check करता है, if grep -q 'debug' /var/log/nginx/access.log; then echo "Debug request found!"; fi जैसी चीजें भी संभव हैं। अभी तक जो नहीं देखा है वह यह कि [ $(expr 1 + 1) -eq 2 ] && [ $(expr 2 + 2) -eq 3 ] लिखना चाहिए या फिर test के built-in logical AND, यानी [ $(expr 1 + 1) -eq 2 -a $(expr 2 + 2) -eq 4 ] का उपयोग करना चाहिए। अगर performance issue नहीं है, तो दोनों ही समान कारणों से valid लगते हैं

    • [ a = b ] && echo "Oops!" || echo "Expected; phew!" को general rule की तरह नहीं लेना चाहिए। शायद bash इस line को ([ a = b ] && echo "Oops!") || echo "Expected; phew!" की तरह interpret करेगा
      इसलिए && के बाद वाली command sequence fail हो जाए तो || के बाद वाला code किसी भी हालत में execute होगा। उदाहरण के लिए अगर >/dev/full echo "strings match" write error से fail हो जाता है, तो strings समान होने के बावजूद "strings don't match" print होगा। यह if block के अर्थ से अलग है
    • इस तरह की shorthand से बचना बेहतर है। अगर आप set -e इस्तेमाल कर रहे हैं, जो करना चाहिए, तो if [ a = b ]; then echo "Oops!"; fi expected तरीके से काम करता है, लेकिन [ a = b ] && echo "Oops!" expression a के b के बराबर न होने पर error के साथ exit कर जाता है
    • POSIX के अनुसार, -a, -o binary primaries और (, ) operators को deprecated के रूप में mark किया गया है। details के लिए https://pubs.opengroup.org/onlinepubs/9699919799/utilities/t... का "Application Usage" देखें
    • चाहे -a इस्तेमाल करें या दो tests और &&, अगर bash की arithmetic evaluation इस्तेमाल कर सकते हैं तो expr चलाने बाहर जाने की जरूरत नहीं है: [ $((1+1)) -eq 2 ]
  • कुछ साल पहले से मैंने [ इस्तेमाल करना छोड़ दिया है। test यह बात मजबूत करता है कि यह syntax नहीं, बल्कि दूसरे commands की तरह सिर्फ एक command है। और man bash में ढूँढने की तुलना में man test कहीं ज्यादा सुविधाजनक है

    • बात पूरी तरह मेल नहीं खाती। GNU Coreutils में man test ही नहीं, man [ manual page भी है
      Bash में quick cheatsheet के तौर पर help test है। [ command बहुत पुराना है, और 1979 के Version 7 Unix में पहले से शामिल था
  • सहमत। [ और bash-विशेष [[ असल में क्या हो रहा है, इस बारे में काफी भ्रम पैदा करते हैं, इसलिए भरोसा करना मुश्किल था
    हालांकि [[ के built-in command होने की guarantee का उस दौर में स्पष्ट उद्देश्य था जब shell script की performance मायने रखती थी, और वह बहुत पुरानी बात भी नहीं है

    • यह लेख पढ़ने के बाद शायद मैं बस test इस्तेमाल करूँगा। bash scripts अक्सर नहीं लिखता, इसलिए if statement, खासकर space rules, में हमेशा अटकता हूँ। वजह समझने के बाद यह बहुत obvious हो गया, और test इस्तेमाल करने पर यह ज्यादा साफ दिखता है कि आप सिर्फ arguments pass कर रहे हैं
  • [ और test में सबसे बड़ा trap एक-argument वाला behavior है। उदाहरण के लिए, किसी variable के non-empty होने की जाँच करने के लिए आप [ -n $FOO ] लिख सकते हैं
    लेकिन अगर FOO set नहीं है, तो वह empty string नहीं बल्कि कुछ भी नहीं में expand होता है, और यह [ -n ] जैसा हो जाता है। POSIX [ के one-argument form में मांग करता है कि अगर वह argument, यहाँ "-n", empty नहीं है तो success होना चाहिए। इसलिए यह गलत report करता है कि $FOO non-empty है। Variables को हमेशा quotes में रखना चाहिए

    • आखिरी sentence को सबसे आगे ले जाना चाहिए। Variables को quotes में रखोtest built-in command के specification में खुद कोई trap नहीं है; trap तो shell में ही है
      जिस behavior का जिक्र है, वह समझ में आता है। [ "$FOO" ] हमेशा यह जाँचने वाला form है कि content non-empty है या नहीं, चाहे content कुछ भी हो, यहाँ तक कि "-n" भी हो
    • Scripts पर ShellCheck चला लेना चाहिए
    • अगर $FOO में spaces हैं तो वह कई arguments में expand होगा। बस हमेशा variables को quotes में रखो
    • [ x"$FOO" != x"" ]
    • इस case में मैं [ -n "${FOO?}" ] इस्तेमाल करूँगा, ताकि $FOO null हो या set न हो तो script तुरंत रुक जाए
  • chubot ने test/[/[[ के ज्यादा subtle हिस्सों में उतरने वाला एक दिलचस्प document लिखा है। उस blog की बाकी posts भी shell की अजीब बातों को काफी दिलचस्प तरीके से समझाती हैं
    ¹ https://www.oilshell.org/blog/2017/08/31.html
    ² https://www.oilshell.org/blog/2016/11/18.html

  • मुझे बिल्कुल पता नहीं था कि [ एक program है, और यह बात थोड़ी मजेदार है कि यह check करता है कि आखिरी argument closing bracket है या नहीं
    फिर भी इससे समझ में आता है कि brackets के दोनों तरफ spaces क्यों चाहिए

  • [[ bash-specific है। अगर आपको पता है कि आप सिर्फ bash इस्तेमाल करेंगे, तो इसे इस्तेमाल करें। Details लेख ने अच्छे से cover की हैं

    • zsh में भी है :)
      https://zsh.sourceforge.io/Doc/Release/Conditional-Expressio...
    • हमेशा [[ इस्तेमाल करना चाहिए
      यह zsh और ksh में भी है, और मुझे लगभग पूरा यकीन है कि इसकी शुरुआत 1988 या उससे पहले के ksh में हुई थी
    • यानी test और [ POSIX में specified हैं और आम तौर पर actual binary के रूप में मौजूद होते हैं। हालांकि shell built-in command उन्हें shadow कर सकता है
      दूसरी तरफ [[ POSIX में specified नहीं है, और आम तौर पर सिर्फ shell built-in के रूप में मौजूद होता है
    • अगर आप explicitly Fish जैसे बिल्कुल अलग shell का इस्तेमाल नहीं कर रहे, तो समझ नहीं आता कि Bash क्यों नहीं इस्तेमाल करेंगे
      सिर्फ lowest-common-denominator shell को target करना पूरी तरह पुराने जमाने की चिंता जैसा लगता है
  • आखिरी if statement क्यों confusing है, यह मुझे ठीक से समझ नहीं आया। अगर वजह यह है कि shell script पहली बार सीखते समय लोग आम तौर पर मान लेते हैं कि [ कोई अलग program नहीं बल्कि bash scripting language का हिस्सा है, तो अब समझ आता है। वरना अच्छा होगा कोई बताए कि इसमें surprising क्या है

    • [ binary है यह न जानने पर भी समझ नहीं आता कि यह confusing क्यों है। यह तो बहुत normal bash जैसा दिखता है
  • Shell को लेकर मेरी preferences strong हैं, लेकिन दुनिया के ज्यादातर लोगों से मेल नहीं खातीं
    मेरा मानना है कि [ कभी इस्तेमाल नहीं करना चाहिए और सिर्फ test इस्तेमाल करना चाहिए। [ यह भ्रम पैदा करता है कि उसका mechanism language syntax का हिस्सा है, जबकि असल में वह बस एक और "program" है। यहाँ "program" में built-in commands और functions भी शामिल हैं। if/||/&& exit status देखते हैं, और एक program, expansion के बाद बस string रह जाने वाले magic variable $? को देखने के अलावा किसी और चीज का exit status नहीं देख सकता। case strings देखता है, लेकिन exit status के आधार पर काम नहीं करता और case ... esac operation के हिस्से के रूप में exit status set भी नहीं करता। "Program" exit status set करता है। साथ ही [/test को सिर्फ filesystem structure evaluate करते समय इस्तेमाल करना चाहिए, जैसे test -f /dev/null। मेरा मानना है कि string evaluation के लिए case इस्तेमाल करना चाहिए। जाहिर है, ज्यादातर scripts देखकर मुझे खुजली होती है, और मेरी लिखी scripts दूसरों को अजीब लगती हैं

    • मजाक मेरे ही ऊपर आ गया। यही तो लेख का point था
      Shell इस्तेमाल करते समय मैं if के बाद program को अलग line में रखकर then पर जाने वाला form पसंद करता हूँ। यह जोर देने के लिए कि if के बाद वाली चीज "then से पहले आखिरी command का exit status देखती है"। उदाहरण के लिए if; ls /tmp/goober; test -d /tmp/goober; echo "I'll always execute the then clause because echo will always return a 0 return code"; then ... fi जैसे structure में if आखिरकार सिर्फ echo का exit status देखता है, इसलिए then clause हमेशा execute होगा
    • उसी रास्ते पर चलने वाला साथी मिल गया। test वाली 8 साल की scripts इस बात का सबूत हैं कि मैं सहमत हूँ। यह अकेला रास्ता है... Google shell style guide को दोष देता हूँ
      sh और bash दोनों में चलने वाली scripts लिखते हुए test को prefer करने की आदत पड़ी, लेकिन character [ को command की तरह treat करने से यह semantically ज्यादा समझ में आता है, इसलिए अभी भी इस्तेमाल कर रहा हूँ। यह भी अजीब है कि ] अलग binary नहीं बल्कि [ का argument है। Technical वजह समझता हूँ, लेकिन hack जैसा लगता है
    • यहाँ comments पढ़कर लगता है कि किसी तरह सिर्फ test इस्तेमाल करने वाले लोग दर्जनों हैं। दर्जनों!
  • जब regex matching करनी हो तभी [[ इस्तेमाल करना सीखा था। जैसे: if [[ "${foo}" =~ ^bar$ ]]; then echo Yes; fi
    बाकी में बस "test" या "[" इस्तेमाल करता हूँ। bash की 85,000 lines लिख चुका हूँ। यह नहीं कह रहा कि bash शानदार है, लेकिन अभी भी कई कामों में मेरी जरूरत पूरी करता है

    • अगर आप POSIX-compatible तरीके से अपेक्षाकृत simple pattern matching करना चाहते हैं, तो expr basic regular expressions match कर सकता है और capture groups भी return कर सकता है

1: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/e...

  • इस बात ने मुझे कुछ समय तक परेशान किया कि regex को ज्यों-का-त्यों लिखा जाता है और quotes में नहीं रखा जाता। मैं ऐसा इंसान हूं जो हर चीज़ को धार्मिक रूप से quotes में डालता है, इसलिए यह समझने में समय लगा कि इतनी बेवकूफ़ी भरी सरल regex match क्यों नहीं हो रही थी।
  • उस उदाहरण का कोई खास मतलब नहीं है। [ "$foo" = bar ] && echo Yes काफी है
    substring matching के लिए [ और * glob आम तौर पर पर्याप्त होते हैं। जैसे [ "$bar" = extra* ] && echo '$bar began with extra'। Bash की regex dialect काफ़ी primitive है, इसलिए उसे मेहनत करके इस्तेमाल करने लायक शायद ही हो। जटिल कामों के लिए grep, awk, perl जैसे दूसरे tools इस्तेमाल करना सही है। जिन complex tasks में ज़्यादा reusability, modularity और built-in types चाहिए, उन्हें भी bash में ही करने की ज़िद करने पर returns तेज़ी से घटने लगते हैं।