यह पोस्ट पहले Stephen की वेबसाइट पर छपी थी और यहाँ अनुमति के साथ (थोड़ा बदलकर) दोबारा प्रकाशित की जा रही है
cal का परिचय
क्या आप जानते हैं कि macOS और Linux की कमांड लाइन में एक कैलेंडर भी होता है?
$ cal
July 2021
Su Mo Tu We Th Fr Sa
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
tldr पर एक नज़र डालने से पता चलता है कि यह टूल कितना काम का हो सकता है
$ tldr cal
cal
Prints calendar information.
- Display a calendar for the current month:
cal
- Display previous, current and next month:
cal -3
- Display a calendar for a specific month (1-12 or name):
cal -m month
- Display a calendar for the current year:
cal -y
- Display a calendar for a specific year (4 digits):
cal year
- Display a calendar for a specific month and year:
cal month year
- Display date of Easter (Western Christian churches) in a given year:
ncal -e year
लेकिन इसमें एक चीज़ की कमी है। अधिवर्ष!
बेशक cal को अधिवर्ष के बारे में पता है, वरना यह कैलेंडर कहलाने लायक ही न होता। लेकिन इसमें उनकी सूची बनाने का कोई तरीका नहीं है।
अधिवर्ष पहचानना, वह भी जुगाड़ से
चलिए इसे खुद बनाते हैं! हम कैलेंडर लाइब्रेरी वाली कोई असली प्रोग्रामिंग भाषा इस्तेमाल कर सकते थे, लेकिन कमांड लाइन के टुकड़ों को जोड़कर चीज़ें बनाना मज़ेदार होता है, और ऐसा भी नहीं है कि हम इस गणना को असली काम में लगाने जा रहे हैं।
सबसे पहले यह मान लेते हैं कि फरवरी ही अधिवर्ष की पहचान है।
$ cal 02 2021
February 2021
Su Mo Tu We Th Fr Sa
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28
$ cal 02 2020
February 2020
Su Mo Tu We Th Fr Sa
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
क्या हम "29" को खोजकर अधिवर्ष पहचान सकते हैं? शायद!
$ cal 02 2020 | grep -q 29 && echo "LEAP" || echo "normal"
LEAP
$ cal 02 2021 | grep -q 29 && echo "LEAP" || echo "normal"
normal
अब तक सब ठीक? लेकिन मुझे लगता है आपको दिक्कत दिख रही है।
$ for year in {2020..2030}; do
printf "$year ";
cal 02 "$year" | grep -q 29 && echo "LEAP" || echo "year";
done
2020 LEAP
2021 year
2022 year
2023 year
2024 LEAP
2025 year
2026 year
2027 year
2028 LEAP
2029 LEAP
2030 year
2029 अधिवर्ष नहीं है। यह सिर्फ इसलिए मैच हो रहा है क्योंकि 2029 में 29 है।
$ cal 02 2029
February 2029
Su Mo Tu We Th Fr Sa
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28
हम "February" वाली पूरी लाइन को हटाकर उस परेशान करने वाले नतीजे को हटा सकते हैं।
$ cal 02 2029 | grep -v Feb
Su Mo Tu We Th Fr Sa
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28
अब मेरा मानना है कि अगर नतीजे में "29" मिल जाए, तो वह वाकई अधिवर्ष है।
$ for year in {2020..2030}; do
printf "$year ";
cal 02 "$year" | grep -v Feb | grep -q 29 && echo "LEAP" || echo "year";
done
2020 LEAP
2021 year
2022 year
2023 year
2024 LEAP
2025 year
2026 year
2027 year
2028 LEAP
2029 year
क्या यह असली अधिवर्षों से मेल खाता है?
Wolfram Research का अधिवर्ष पेज पर 21वीं सदी के पहले हिस्से के अधिवर्षों की यह सूची दी गई है।
2000
2004
2008
2012
2016
2020
2024
2028
2032
2036
2040
2044
2048
चलिए देखते हैं हमारा नतीजा कैसा रहता है
$ for year in {2000..2051}; do cal 02 "$year" | grep -v Feb | grep -q 29 && echo $year; done
2000
2004
2008
2012
2016
2020
2024
2028
2032
2036
2040
2044
2048
हो गया!
लेकिन 29 को grep करना बहुत आसान है। चलिए इसे और अजीब बनाते हैं!
अधिवर्ष पहचानना, वह भी और भी जुगाड़ से
अधिवर्ष और सामान्य वर्ष में फर्क बस 29 तारीख का वह अतिरिक्त दिन है। क्या हम cal के आउटपुट में अक्षरों की संख्या गिनकर अधिवर्ष पहचान सकते हैं?
हाँ, हम पहचान सकते हैं। लेकिन पहले एक अड़चन पार करनी है। आइए, मैं दिखाता हूँ कि मेरा मतलब क्या है।
हम जानते हैं कि 2020 अधिवर्ष है और 2021 नहीं। चलिए देखते हैं कि इन दोनों सालों की फरवरी के आउटपुट में अक्षरों की संख्या में क्या फर्क है। इसके लिए हम काम का wc टूल इस्तेमाल करेंगे, खास तौर पर wc -c, जो बाइट अक्षर गिनता है।
$ printf "hello" | wc -c
5
"hello" में 5 अक्षर हैं (echo की जगह printf इस्तेमाल किया, वरना एक न्यूलाइन अक्षर भी जुड़ जाता)। बढ़िया!
$ cal 02 2020 | wc -c
184
$ cal 02 2021 | wc -c
184
यही अड़चन है। दोनों महीनों का आउटपुट अलग है (2020 में 29 तारीख वाला दिन है), फिर भी दोनों में अक्षरों की संख्या एक जैसी है, क्योंकि cal एक जैसी फॉर्मेटिंग के लिए स्पेस जोड़ता है। और स्पेस भी संख्याओं की तरह ही गिने जाने वाले अक्षर हैं।
अब क्या करें?
हम सारे स्पेस हटा सकते हैं! तब सिर्फ वही अक्षर बचेंगे जिन्हें हम गिनना चाहते हैं।
अक्षर हटाने के लिए tr कमांड बहुत काम की है। tr कमांड स्ट्रिंग डेटा को बदलती है। जैसे, यह सारे a अक्षरों को A में बदल सकती है।
$ echo "aardvark" | tr a A
AArdvArk
यह बताए गए सारे अक्षरों को मिटा भी सकती है, और हमें यही चाहिए।
हमारे मामले में हमें सारे व्हाइटस्पेस अक्षर मिटाने हैं, चाहे वे किसी भी तरह के हों (स्पेस, टैब, न्यूलाइन वगैरह)। इसके लिए एक काम का समूह है जिसे कैरेक्टर क्लास कहते हैं, और यह ठीक यही काम करता है। [:space:] कैरेक्टर क्लास इन सब तरह के व्हाइटस्पेस अक्षरों को पकड़ता है।
$ cal 02 2020 | tr -d '[:space:]'
February2020SuMoTuWeThFrSa1234567891011121314151617181920212223242526272829
$ cal 02 2021 | tr -d '[:space:]'
February2021SuMoTuWeThFrSa12345678910111213141516171819202122232425262728
बढ़िया! अब क्या हम अक्षरों की संख्या गिनकर अधिवर्ष ढूँढ सकते हैं? बिल्कुल!
$ cal 02 2020 | tr -d '[:space:]' | wc -c
75
$ cal 02 2021 | tr -d '[:space:]' | wc -c
73
अब चलिए कुछ अधिवर्ष छापते हैं!
$ for year in {2000..2051}; do
cal 02 "$year" |
tr -d '[:space:]' |
wc -c |
grep -q 75 && echo "$year";
done
2000
2004
2008
2012
2016
2020
2024
2028
2032
2036
2040
2044
2048
एक के ऊपर एक जुगाड़, और यह काम कर गया!
आपको क्या लगता है, कौन सा तरीका तेज़ है? मुझे भी नहीं पता। "February हटाने" वाला तरीका शायद तेज़ है? लेकिन अगर इनमें बहुत बड़ा फर्क हो, तो मुझे हैरानी होगी।
चलिए हैरान होने की ज़रूरत नहीं, खुद पता करते हैं!
एक बढ़िया टूल है जिसका नाम hyperfine है, जो कई कमांड लाइन कॉल के प्रदर्शन को जाँचने देता है
कौन तेज़ है? Hyperfine बताएगा
मैं hyperfine को दोनों कमांड के साथ ये फ्लैग दे रहा हूँ।
--style basic
Plain output styling
--export-markdown hyperfine.md
Export results as markdown
--warmup 5
Do five runs before benchmarking
--ignore-failure
Ignore non-zero exits
हमें सिर्फ एक साल की गणना का बेंचमार्क देखना है, सालों का लूप बनाने में लगने वाला समय नहीं। इसलिए मैं दोनों तरीकों से सिर्फ किसी एक खास साल के लिए अधिवर्ष जाँचने का बेंचमार्क लूँगा।
$ hyperfine --style basic --export-markdown hyperfine.md --warmup 5 --ignore-failure "cal 02 2050 | grep -v Feb | grep -q 29" "cal 02 2050 | tr -d '[:space:]' | wc -c | grep -q 75"
Benchmark #1: cal 02 2050 | grep -v Feb | grep -q 29
Time (mean ± σ): 1.8 ms ± 0.3 ms [User: 0.7 ms, System: 1.9 ms]
Range (min … max): 1.0 ms … 3.6 ms 493 runs
Warning: Command took less than 5 ms to complete. Results might be inaccurate.
Warning: Ignoring non-zero exit code.
Benchmark #2: cal 02 2050 | tr -d '[:space:]' | wc -c | grep -q 75
Time (mean ± σ): 1.9 ms ± 0.4 ms [User: 0.9 ms, System: 2.7 ms]
Range (min … max): 0.7 ms … 5.9 ms 811 runs
Warning: Command took less than 5 ms to complete. Results might be inaccurate.
Warning: Ignoring non-zero exit code.
Summary
'cal 02 2050 | grep -v Feb | grep -q 29' ran
1.07 ± 0.28 times faster than 'cal 02 2050 | tr -d '[:space:]' | wc -c | grep -q 75'
| कमांड | औसत [ms] | न्यूनतम [ms] | अधिकतम [ms] | सापेक्ष |
|---|---|---|---|---|
cal 02 2050 | grep -v Feb | grep -q 29 |
1.8 ± 0.3 | 1.0 | 3.6 | 1.00 |
cal 02 2050 | tr -d '[:space:]' | wc -c | grep -q 75 |
1.9 ± 0.4 | 0.7 | 5.9 | 1.07 ± 0.28 |
जैसा कि अपेक्षित था, असली समय के हिसाब से दोनों में लगभग कोई फर्क नहीं है, लेकिन "February हटाने" वाला तरीका थोड़ा तेज़ हो सकता है।
और यहीं यह कमांड लाइन टूल जोड़ने वाली यात्रा खत्म होती है।
सारांश
- पहले से मौजूद टूल को एक डेटा पाइपलाइन में जोड़कर आप कमांड लाइन के नए फीचर बना सकते हैं
- ज़रा-सी रचनात्मकता से साधारण टुकड़ों से हैरान करने वाले जटिल नतीजे मिल सकते हैं
- कमांड लाइन कमांड का बेंचमार्क लेने के लिए Hyperfine एक काम का टूल है
आगे क्या करें
-
calकमांड इस्तेमाल करके देखिए। - पूरे साल के सारे नॉन-स्पेस अक्षर गिनने वाली एक पाइपलाइन बनाकर देखिए:
cal 2020 - 1750 से 1759 तक हर साल के लिए वही पूरे साल की गिनती वाली पाइपलाइन चलाकर देखिए: कुछ अजीब दिखता है?