- Unix जैसे सिस्टमों में
/bin/[नाम की एक executable file हो सकती है, जिसका नाम सिर्फ एक अक्षर वाला symbol है, और जो syntax shell conditional expression जैसा दिखता है वह असल में command execution और exit code पर आधारित होता है testexpression को 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/[दिखाता है
- उदाहरण command
/bin/[और/bin/testएक ही binary की ओर point कर सकते हैं- उदाहरण में दोनों paths एक ही inode और size वाली files के रूप में दिखते हैं
- हालांकि हर system में उनका hard link होना जरूरी नहीं है
testshell में 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]है या नहीं
ifstatement 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 btest: a: unexpected operatorदेता हैtest a bdash: 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 matchmatchprint करता है[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 code0होता है- अगर पहला
grepfail हो जाए, तो&&के बाद वाला command सफल नहीं होता और overall exit code1होता है
- इसलिए
test/[expressions और shell logical operators को एक ही conditional statement में combine किया जा सकता है- उदाहरण:
[ a = b ] || grep -q ^hello$ /usr/share/dict/words
- उदाहरण:
- POSIX यह require नहीं करता कि
/bin/[और/bin/testhard links हों- NetBSD में वे hard links थे
- macOS Catalina एक ही binary की अलग copies देता है
- Debian testing अलग-अलग binaries देता है
- POSIX specification यह require नहीं करता कि दोनों files linked हों
1 टिप्पणियां
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 करने के लिए
functionkeyword जरूरी होता है।makeका$(shell)कई targets build करते समय performance में measurable फर्क ला सकता है। फिर भी जब कुछ भी नहीं करना हो तो यह नुकसान है, इसलिए आम तौर पर regeneration trigger करने के लिएincludeइस्तेमाल करना सही रहता है। GNU का POSIX को ignore करना पूरी तरह वाजिब है, क्योंकि POSIX ज्यादातर वास्तविक समस्याएँ हल करने में बहुत उपयोगी नहीं है.के बिना 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 नहीं है क्या?make $(shell …) expansionकी तरह दिया गया है, जबकि मूल रूप सेmake $(shell ...) expansionहोना चाहिएbody में जैसे सही लिखा है, यह तीन dots हैं, एक ellipsis नहीं, इसलिए
mldrखुद भी सही नहीं है। शायद दो unrelated bugs ने एक साथ असर डाला हैआखिरी point को एक कदम और आगे बढ़ाएँ तो
ifblock खुद भी हटाया जा सकता है।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 करते समय कभी-कभी उपयोगी होता है। इस तथ्य की वजह से किifblock सामान्य 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 होगा। यहifblock के अर्थ से अलग हैset -eइस्तेमाल कर रहे हैं, जो करना चाहिए, तोif [ a = b ]; then echo "Oops!"; fiexpected तरीके से काम करता है, लेकिन[ a = b ] && echo "Oops!"expressionaकेbके बराबर न होने पर error के साथ exit कर जाता है-a,-obinary 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कहीं ज्यादा सुविधाजनक है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 अक्सर नहीं लिखता, इसलिएifstatement, खासकर space rules, में हमेशा अटकता हूँ। वजह समझने के बाद यह बहुत obvious हो गया, औरtestइस्तेमाल करने पर यह ज्यादा साफ दिखता है कि आप सिर्फ arguments pass कर रहे हैं[औरtestमें सबसे बड़ा trap एक-argument वाला behavior है। उदाहरण के लिए, किसी variable के non-empty होने की जाँच करने के लिए आप[ -n $FOO ]लिख सकते हैंलेकिन अगर
FOOset नहीं है, तो वह empty string नहीं बल्कि कुछ भी नहीं में expand होता है, और यह[ -n ]जैसा हो जाता है। POSIX[के one-argument form में मांग करता है कि अगर वह argument, यहाँ"-n", empty नहीं है तो success होना चाहिए। इसलिए यह गलत report करता है कि$FOOnon-empty है। Variables को हमेशा quotes में रखना चाहिएtestbuilt-in command के specification में खुद कोई trap नहीं है; trap तो shell में ही हैजिस behavior का जिक्र है, वह समझ में आता है।
[ "$FOO" ]हमेशा यह जाँचने वाला form है कि content non-empty है या नहीं, चाहे content कुछ भी हो, यहाँ तक कि"-n"भी हो$FOOमें spaces हैं तो वह कई arguments में expand होगा। बस हमेशा variables को quotes में रखो[ x"$FOO" != x"" ][ -n "${FOO?}" ]इस्तेमाल करूँगा, ताकि$FOOnull हो या 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 की हैं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 के रूप में मौजूद होता हैसिर्फ lowest-common-denominator shell को target करना पूरी तरह पुराने जमाने की चिंता जैसा लगता है
आखिरी
ifstatement क्यों 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 नहीं देख सकता।casestrings देखता है, लेकिन exit status के आधार पर काम नहीं करता औरcase ... esacoperation के हिस्से के रूप में exit status set भी नहीं करता। "Program" exit status set करता है। साथ ही[/testको सिर्फ filesystem structure evaluate करते समय इस्तेमाल करना चाहिए, जैसेtest -f /dev/null। मेरा मानना है कि string evaluation के लिएcaseइस्तेमाल करना चाहिए। जाहिर है, ज्यादातर scripts देखकर मुझे खुजली होती है, और मेरी लिखी scripts दूसरों को अजीब लगती हैं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 जैसा लगता हैtestइस्तेमाल करने वाले लोग दर्जनों हैं। दर्जनों!जब regex matching करनी हो तभी
[[इस्तेमाल करना सीखा था। जैसे:if [[ "${foo}" =~ ^bar$ ]]; then echo Yes; fiबाकी में बस
"test"या"["इस्तेमाल करता हूँ। bash की 85,000 lines लिख चुका हूँ। यह नहीं कह रहा कि bash शानदार है, लेकिन अभी भी कई कामों में मेरी जरूरत पूरी करता हैexprbasic regular expressions match कर सकता है और capture groups भी return कर सकता है1: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/e...
[ "$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 तेज़ी से घटने लगते हैं।