এই পোস্টটি প্রথমে 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
ফেব্রুয়ারি লেখা লাইনটা বাদ দিলেই আমরা ওই ঝামেলার ফলাফলটা সরিয়ে ফেলতে পারি।
$ 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-এর লিপ ইয়ার পেজে।
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 করা খুব সহজ ব্যাপার। চলুন আরও অদ্ভুত কিছু করি!
আরও বেশি হ্যাকের ভঙ্গিতে লিপ ইয়ার চেনা
লিপ ইয়ার আর সাধারণ বছরের তফাতটা হলো ২৯ তারিখের ওই বাড়তি দিনটা। cal-এর আউটপুটে ক্যারেক্টারের সংখ্যা দেখে কি আমরা লিপ ইয়ার চিনতে পারি?
হ্যাঁ, পারি। কিন্তু আগে একটা ফাঁদ পেরোতে হবে। কী বোঝাচ্ছি, দেখাচ্ছি।
আমরা জানি 2020 লিপ ইয়ার আর 2021 লিপ ইয়ার নয়। এই দুই বছরের ফেব্রুয়ারির আউটপুটে ক্যারেক্টারের সংখ্যার তফাতটা দেখে নিই। কাজটার জন্য কাজের মতো wc ইউটিলিটি ব্যবহার করব, বিশেষ করে বাইট ক্যারেক্টার গোনার জন্য wc -c।
$ printf "hello" | wc -c
5
"hello"-তে ৫টি ক্যারেক্টার (echo-র বদলে printf ব্যবহার করেছি, নইলে একটা নিউলাইন ক্যারেক্টারও যোগ হতো)। দারুণ!
$ cal 02 2020 | wc -c
184
$ cal 02 2021 | wc -c
184
এই তো সেই বাধা। প্রতি মাসের আউটপুট আলাদা (2020-এ ওই ২৯ তারিখটা আছে), কিন্তু দুটোতেই ক্যারেক্টারের সংখ্যা একই, কারণ 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
হ্যাকের পর হ্যাক, আর এটা কাজ করে!
আপনার মতে কোনটা দ্রুত? হ্যাঁ, আমারও কোনো ধারণা নেই। "ফেব্রুয়ারি বাদ দেওয়ার" পথটা হয়তো একটু দ্রুত? কিন্তু বিশাল কোনো তফাত থাকলে অবাক হব।
চলুন অবাক না হই। চলুন খুঁজে দেখি!
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 |
যেমনটা আশা করেছিলাম, ওয়াল ক্লক টাইমের হিসাবে দুটোর মধ্যে কার্যত কোনো তফাত নেই, তবে "ফেব্রুয়ারি বাদ দেওয়ার" পথটা হয়তো একটু দ্রুত।
এই নিয়ে কমান্ড লাইন টুল জোড়া লাগানোর যাত্রা শেষ হলো।
শেষ কথা
- আগে থেকে থাকা টুলগুলোকে একটি ডেটা পাইপলাইনে জোড়া লাগিয়ে আপনি নতুন কমান্ড লাইন ফিচার বানাতে পারেন
- সামান্য সৃজনশীলতা দিয়ে সাধারণ সাধারণ অংশ থেকেই অবাক করার মতো জটিল ফলাফল পাওয়া যায়
- কমান্ড লাইন কমান্ডের বেঞ্চমার্ক করার জন্য Hyperfine একটা কাজের টুল
পরের ধাপ
-
calকমান্ড ব্যবহার করে ঘুরে দেখুন। - একটা পুরো বছরের সব নন-স্পেস ক্যারেক্টার গোনে এমন একটা পাইপলাইন বানিয়ে দেখুন:
cal 2020 - 1750 থেকে 1759 পর্যন্ত প্রতিটি বছরের জন্য ওই পুরো বছর গোনার পাইপলাইনটা চালিয়ে দেখুন: কিছু অদ্ভুত লাগছে?