ظهر هذا المقال في الأصل على موقع 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 ليست سنة كبيسة. إنها تتطابق فقط بسبب الرقم 29 في 2029.
$ 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 على هذه القائمة بوصفها السنوات الكبيسة في النصف الأول من القرن الحادي والعشرين.
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
5 أحرف في "hello" (استخدمنا printf بدلًا من echo، وإلا لكان لدينا محرف سطر جديد أيضًا). رائع!
$ 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: هل تلاحظ شيئًا غريبًا؟