ToCode
Ir al canal en Telegram
1 419
Suscriptores
Sin datos24 horas
Sin datos7 días
Sin datos30 días
Archivo de publicaciones
1 419
# לא יודע לכתוב
אחד השלבים המתסכלים בלימוד טכנולוגיה או שפה מגיע אחרי שאנחנו כבר מבינים את הבסיס, מצליחים לפתור תרגילים פשוטים ואפילו מצליחים להבין קוד שאנחנו רואים בפיתרון או תוכנית דוגמה.
אבל לא מצליחים לכתוב.
זה יכול להיות שאנחנו נתקעים ממש בהתחלה, פוחדים שנבחר את התבנית הלא נכונה או לא יודעים בדיוק איזו תבנית לבחור. חוששים שנעשה צעד שעוד שבוע נתחרט עליו, או הכי גרוע - מתחילים לכתוב והכל יוצא יותר מסובך בהרבה מכל דוגמה שראינו, עד לרמה שקשה להבין לאן נעלם כל הסיבוך הזה בתוכניות הדוגמה, ואם מישהו לא עשה עלינו איזה תרגיל.
אבל אין שום תרגיל.
הדרך היחידה ללמוד לכתוב היא לכתוב, ולכתוב כמה שיותר. לעבור את השלב הזה שדברים לא עובדים, ואז את השלב שהם מסובכים, ואז לאט לאט לשים לב לפיתרונות יותר פשוטים עד שדברים מתחילים להתחבר. המשך לימוד בקורס, המשך קריאה או אפילו שיעורים פרטיים לא יעזרו כאן. השרירים של הכתיבה צריכים להתרגל שגם הם צריכים לעבוד.
התחושה שאני לא יודע לכתוב היא הסימן הכי טוב לסיים את הקריאה ולהתחיל לעבוד.
1 419
# מקום לטעויות
בכתיבת מערכות תוכנה אנחנו יודעים (בדרך כלל) לאזן בין מהירות הפיתוח לביטחון. בקצה אחד של הסקאלה, מי שבונה קוצב לב צריך לעשות הכל כדי להגיע לאפס טעויות, ובקצה השני אם אתם בונים אתר משחקים אונליין אתם מוכנים לחיות עם העלאת גירסה שבה משחק מסוים לא עבד או עבד עם באג. תמיד אפשר להעלות גירסת תיקון.
את אותו הגיון אפשר לקחת גם לקריירה האישית או הלימודית שלנו-
1. האם אני מוכנה ללמוד טכנולוגיה גם אם היא בסוף לא תהיה "הדבר הגדול הבא"? או שהזמן שלי קצוב ואני צריכה אפס טעויות בבחירת נושאי הלימוד?
2. האם אני מוכנה להתפרץ בדיון גם אם אני לא בטוחה במה שיש לי להגיד, או שאני שואפת ל"אפס טעויות" ומוכנה לדבר פחות ובלבד שתמיד אגיד דברים שאני בטוחה בהם.
3. האם אני מוכנה להעביר הרצאה לצוות על נושא שאני לא מומחית בו, או שאני מעדיפה לא לקחת סיכונים ולכוון ל"אפס טעויות".
הטריק הוא שכמו במערכות, אי אפשר להחזיק במקל בשני הקצוות. אפס טעויות מכתיב התקדמות איטית יותר ומעט מאוד חדשנות. לרוב המתכנתים והמתכנתות שאני מכיר, מדיניות של אפס טעויות בקריירה לא שווה את המאמץ.
1 419
# היום למדתי: גיט ריבייס ו Merge Commits
הדבר שאני הכי אוהב בעבודה עם לקוחות הוא כשמתקילים אותי בשאלה שלא חשבתי עליה לפני. כך היה כשעזרתי היום ללקוח עם גיט ובדרך למדתי על מתג סופר שימושי של rebase.
## נקודת ההתחלה
הסיפור שלנו מתחיל בענף, ויותר מדויק ענף שחי ועובדים עליו לאורך זמן. בגלל שהענף הראשי גם מתקדם בזמן הזה, לפעמים רוצים לעשות merge מהענף הראשי חזרה לתוך ענף הפיתוח, כדי לא לצבור פער "גדול מדי" ולא לקבל יותר מדי שינויים כשנצטרך למזג את הפיצ'ר בסוף הפיתוח.
לוג טיפוסי של כזה מאגר יכול להיראות כך:
* 2fa580d (main) m12
* e026ae8 m11
* df2c82f m10
| * 8056178 (HEAD -> feature) d6
| * 778c404 d5
| * a6fff44 Merge branch 'main' into feature
| |\
| |/
|/|
* | 80586ce hotfix1
| * a0d9a73 d4
| * 61e4ba0 d3
| * 6ae739d d2
| * 4a0a511 d1
|/
* 09ac820 c3
* ede6816 c2
* 8197d89 c1
ענף הפיתוח הוא feature והוא יוצא מ c3. באמצע היה לנו קומיט עם הטקסט hotfix1 ב main ואותו גם לקחנו לענף הפיתוח במיזוג שקרה אחרי d4, ואחרי אותו מיזוג הענף הראשי התקדם עם m10 ו m11, וענף הפיתוח התקדם עם d5 ו d6.
## הצרות מתחילות
עכשיו אנחנו שולחים Pull Request ומי שעובר עליה לא מרוצה מכל הקומיטים. הם מבקשים למזג את d1, d2 ו d3 לקומיט יחיד ולשנות את הטקסט של d5. בשביל זה אני מפעיל פקודת ריבייס אינטרקטיבי:
$ git switch feature
$ git rebase -i --onto 09ac820 09ac820
אני בוחר את הפקודות הנכונות בריבייס ומוודא בלוג שהכל תקין:
$ git log --oneline --graph
* a877c74 (HEAD -> feature) d6
* b3247e7 fixed text for d5
* c574ede hotfix1
* ddb80ac d4
* 24b7b77 d1
* 09ac820 c3
* ede6816 c2
* 8197d89 c1
וממזג את התוצאה ל main:
$ git switch main
$ git merge feature
אבל עכשיו הלוג שלי שבור:
* 9b5f10a (HEAD -> main) Merge branch 'feature'
|\
| * a877c74 (feature) d6
| * b3247e7 fixed text for d5
| * c574ede hotfix1
| * ddb80ac d4
| * 24b7b77 d1
* | 2fa580d m12
* | e026ae8 m11
* | df2c82f m10
* | 80586ce hotfix1
|/
* 09ac820 c3
* ede6816 c2
* 8197d89 c1
הבעיה היא ש hotfix מופיע גם בענף הראשי וגם בענף ה feature בתור קומיט כפול, במקום לראות merge מסודר מ main ל feature.
## תיקון פשוט עם rebase-merges
מתג הפלא לריבייס שמתקן את הבלאגן נקרא --rebase-merges או בקיצור -r. כשאני מעביר אותו מסך הריבייס האינטרקטיבי כולל גם את ה Merge Commits. זה נראה ככה:
$ git rebase --rebase-merges -i --onto 09ac820 09ac820
והתוצאה:
label onto
# Branch main
reset onto
pick 80586ce hotfix1 # empty
label main
reset onto
pick 4a0a511 d1 # empty
pick 6ae739d d2 # empty
pick 61e4ba0 d3 # empty
pick a0d9a73 d4 # empty
merge -C a6fff44 main # Merge branch 'main' into feature
pick 778c404 d5 # empty
pick 8056178 d6 # empty
הבלוק העליון מגדיר שאנחנו רוצים לקחת את הקומיט hotfix1 ולקרוא לו main. בבלוק התחתון פקודת ה merge אומרת שאנחנו צריכים ליצור merge commit עם הקומיט hotfix1.
כשאני בוחר את ההוראות הנכונות בריבייס הפעם אני מקבל את רשימת ההוראות:
label onto
# Branch main
reset onto
pick 80586ce hotfix1 # empty
label main
reset onto
pick 4a0a511 d1 # empty
s 6ae739d d2 # empty
s 61e4ba0 d3 # empty
pick a0d9a73 d4 # empty
merge -C a6fff44 main # Merge branch 'main' into feature
r 778c404 d5 # empty
pick 8056178 d6 # empty
ואחרי מיזוג ל main נקבל:
* e4c1e0c (HEAD -> main) Merge branch 'feature'
|\
| * bcbb528 (feature) d6
| * 4ef376c fixed text for d5
| * ed81791 Merge branch 'main' into feature
| |\
| * | 35db291 d4
| * | 7296128 d1
* | | 2fa580d m12
* | | e026ae8 m11
* | | df2c82f m10
| |/
|/|
* | 80586ce hotfix1
|/
* 09ac820 c3
* ede6816 c2
* 8197d89 c1
וזו בדיוק ההיסטוריה הנכונה פרט לקומיטים שערכנו עם הריבייס.1 419
$ wc /etc/shells
12 32 209 /etc/shells
השורות והמילים יצאו בסדר, אבל בתווים יש לי בעיה - הפונקציה length מחזירה כמה תווים יש בשורה אבל לא סופרת את תו ירידת השורה שבסופה, בניגוד ל wc שכן כולל אותו. אני מתקן ומקבל:
$ awk '// { words += NF; chars += 1 + length } END { printf "Lines: %d, Words: %d, Chars: %d\n", NR, words, chars }' /etc/shells
Lines: 12, Words: 32, Chars: 209
ופה אנחנו גם לומדים שאפשר להעביר ל awk שם קובץ, ואז הוא יקרא את השורות מהקובץ במקום מהקלט הסטנדרטי.
## שינוי תו ההפרדה
ברירת המחדל של awk היא להפריד בין מילים באמצעות רווחים. אם נעביר ל awk את המתג -F נוכל לקבוע כל תו הפרדה אחר שנרצה. לדוגמה כשיש לי רשימת קבצים בתיקיה אני יכול להשתמש בתו הפרדה נקודה כדי להדפיס רק את שם הקובץ בלי הסיומת:
$ ls | awk -F . '{print $1}'
## הפקודה system ושינוי שמות ברשימה של קבצים
אפשר להשתמש במנגנון הזה כדי להחליף סיומות של מספר קבצים בפקודה אחת. הפונקציה system של awk מאפשרת להפעיל פקודת מערכת. למשל אני יכול לכתוב:
$ ls | awk 'END { system("cowsay " NR) }'
ולקבל פלט כמו:
____
< 24 >
----
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
באותו אופן אני יכול להשתמש ב mv כדי לשנות שמות, ולהפעיל את ה system בכל שורת קלט שיש בה קובץ. הדוגמה הבאה מחליפה את כל הקבצים מסוימת txt לסיומת md:
$ ls *.txt | awk -F . '{ system("mv " $0 " " $1 ".md") }'
הפרמטר שהעברתי ל system הוא מחרוזת אחת שהורכבה בצורה דינמית, בגלל שב awk רווח הוא תו שרשור מחרוזות. הרבה פעמים נוח להשתמש בפונקציה sprintf כדי לבנות מחרוזות כאלה בצורה קצת יותר פשוטה:
$ ls *.txt | awk -F . '{ system(sprintf("mv \"%s\" \"%s%s\"", $0, $1, ".md")) }'
## לולאות וחישוב תדירות של מילים
ועוד מעניין לגלות ש awk יכולה להיות ממש שפת תכנות, עם מערכים ולולאים ואפילו מילונים. הדוגמה האחרונה שלנו תשלב את היכולות האלה כדי לחשב תדירות הופעה של מילים בקלט: נכתוב תוכנית awk שמקבלת קובץ ומדפיסה כמה פעמים כל מילה מופיעה בו.
המשחק כאן הוא לחלק את הקובץ למילים, ואז לרוץ בלולאה על כל המילים ולשמור כל מילה במילון, כאשר המפתח הוא המילה והערך הוא כמה פעמים היא מופיעה. בפעם הראשונה שאנחנו נתקלים במילה היא תיכנס למילון עם ערך 1, ואם היא כבר במילון אז הערך יעלה ב-1 כל פעם שניתקל בה שוב.
כדי להשתמש במילון ב awk אני יכול להוסיף לשם משתנה סוגריים מרובעים בכתיבה. לדוגמה אם אני מסתכל רק על המילה הראשונה אני יכול לכתוב קוד כזה:
$ awk '{ COUNT[$1] += 1 }'
אנחנו כמובן רוצים משהו יותר מתוחכם. במקום לשמור רק את המילה הראשונה אני רוצה לשמור את כל המילים בשורה, ובשביל זה אני משתמש בלולאה. ב awk הפקודה for עובדת בתוך בלוק הפקודה בדיוק כמו שהייתם מצפים מ c, ולכן אני יכול לכתוב:
$ awk '{ for(i=1; i<=NF; i++) { COUNT[$i] += 1 } }'
הפעלה כזאת תרוץ על כל שורות הקלט, תפצל כל שורה למילים ותשמור במילון לכל מילה כמה פעמים היא הופיעה. נשאר לי רק להדפיס את המילון בסיום, ובשביל זה אני צריך להשתמש בעוד לולאה. הפעם אני רוצה לרוץ על מילון ולכן משתמש בלולאת for ... in:
$ awk '{ for(i=1; i<=NF; i++) { COUNT[$i] += 1 } } END { for (word in COUNT) { print word " appeared " COUNT[word] " times" } }'
## איפה ללמוד עוד
בפוסט זה כיסיתי רק חלק קטן מהיכולות של awk כדי לתת לכם טעימה ממה שהפקודה מסוגלת לעשות. אם הדוגמאות פה גרמו לכם להרגיש שזה הכלי הטוב בעולם וששווה להשקיע בו יותר - אז תשמחו לשמוע שאתם בחברה טובה. הנה כמה מקורות לימוד על awk שיעזרו לכם להמשיך להתאמן עליו:
1. הגירסה של awk שמגיעה עם הרבה מערכות יוניקס נקראת gawk. ספר ההדרכה שלהם מעולה:
https://www.gnu.org/software/gawk/manual/gawk.html
2. המדריך Handy One Line Scripts For Awk של אריק פמנט כולל אינסוף רעיונות יצירתיים לשימוש ב awk:
https://www.pement.org/awk/awk1line.txt1 419
ראינו כבר את התנאי המיוחד END, ותשמחו לשמוע שיש לו חבר - התנאי המיוחד BEGIN. ב BEGIN נשתמש כשנרצה לעשות משהו לפני שורת הקלט הראשונה, למשל אני יכול לדמיין באותה פקודה מקודת שאני רוצה להדפיס שורות כותרות מיוחדות לכל עמודה בטבלה. במצב כזה אני משתמש ב BEGIN באופן הבא:
ls -l | awk 'BEGIN { printf "%-41s | %-20s | %-13s\n", "Name", "Owner", "Permissions" } /^d/ { printf "Directory: %-30s | Owned By: %-10s | Permissions: %-10s\n", $9, $3, $1 }'
והתוצאה:
Name | Owner | Permissions
Directory: app | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: bin | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: config | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: db | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: design | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: doc | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: engines | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: lib | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: log | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: public | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: storage | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: test | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: tmp | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: vendor | Owned By: ynonp | Permissions: drwxr-xr-x
פה תהיה נקודה טובה לקחת נשימה ולשים לב שהתוכנית שלנו מתחילה להסתבך. במצב כזה משתלם להוציא את התוכנית לקובץ טקסט חיצוני, וככה יהיה יותר קל לתחזק אותה ולהוסיף עוד בלוקים. אני יוצר קובץ בשם info.awk עם התוכן הבא:
BEGIN {
printf "%-41s | %-20s | %-13s\n", "Name", "Owner", "Permissions";
printf "======================================================================================================\n";
}
/^d/ {
printf "Directory: %-30s | Owned By: %-10s | Permissions: %-10s\n", $9, $3, $1
}
ומפעיל את ה awk שלי עם הפקודה:
$ ls -l | awk -f info.awk
כשמעבירים -f ואז שם קובץ ל awk אז הוא קורא את התוכנית מקובץ חיצוני.
שני המשתנים המיוחדים הבאים ברשימה שלי, NF ו NR, גם יכולים לעזור לנו בתנאים או בפקודות. המשתנה NR מחזיק תמיד את מספר השורה הנוכחית, ו NF את מספר המילים בשורה.
עכשיו כשאני יודע את מספר השורה הנוכחית אני יכול לכתוב תוכנית awk שתחקה את ההתנהגות של head, כלומר תדפיס רק 10 שורות ראשונות מהקלט:
$ man bash| awk 'NR < 10 { print }'
ושמתם לב אני מקווה איך הורדתי את ה $0 והפעלתי print ללא פרמטרים? ברירת המחדל של print כשלא מעבירים פרמטרים היא להדפיס את השורה המלאה.
ומה עם NF? הוא יעזור לנו בדוגמה הבאה כשנרצה לבנות את wc.
## הפונקציה length
נמשיך לדוגמה נוספת עם awk, והפעם נרצה לבנות wc בעזרת awk, כלומר אני רוצה תוכנית awk שתדפיס כמה שורות, תווים ומילים יש בקלט.
אני יודע שמספר השורה הנוכחית שלי הוא NR, ולכן בסוף הקלט משתנה זה יחזיק בדיוק את מספר השורות שהיו בקלט.
אני גם יודע שמספר המילים בשורה שלי הוא NF, ולכן אני יכול לסכום את NF של כל שורה ולהדפיס אותו בסוף הקלט כדי לקבל את מספר המילים הכולל בקלט.
שילוב שני הדברים מתרגם ל awk הבא:
$ cat /etc/shells | awk '// { words += NF } END { printf "Lines: %d, Words: %d, Chars: %d\n", NR, words, chars }'
לחישוב מספר התווים אני משתמש בפונקציה length של awk, שמחזירה לי את מספר התווים בשורה או במילה שהיא קיבלה כקלט. במקרה שלנו:
$ cat /etc/shells | awk '// { words += NF; chars += length } END { printf "Lines: %d, Words: %d, Chars: %d\n", NR, words, chars }'
אבל אם תבדקו תגלו שהתוצאה לא זהה ל wc. ככה נראית אצלי הפעלה של wc על אותו קובץ קלט:1 419
$ ls -l | awk '/^d/ { count += 1 } END { print count }'
וזה כבר נותן לי הדפסה אחת של המספר 25.
בעזרת משתנים אני יכול לבצע עוד אינסוף חישובים יצירתיים. אם נישאר עם הפלט של ls -l, אני יכול להיזכר שהמילה החמישית בפלט היא גודל הקובץ, ולחשב את סכום הגדלים של כל הקבצים בתיקיה:
$ ls -l | awk '/^-/ { size += $5 } END { print size }'
שימו לב ששיניתי את התנאי כדי לספור גדלים רק של שורות שמייצגות קבצים, כי אני יודע ששורה שמייצגת קובץ מתחילה בסימן -.
## פקודות הדפסה print, printf
ננסה עוד פקודת הדפסה:
$ ls -l | awk '/^d/ { print "Directory: " $9, "Owned By " $3, "Permissions: " $1 }'
הפעם אני מעביר ל print שלושה ביטויים: הראשון הוא "Directory: " $9, השני הוא "Owned By " $3 והשלישי "Permissions: " $1. הביטויים מופרדים בפסיקים, וכל ביטוי כולל שתי מחרוזות מופרדות ברווח. ב awk רווח הוא בעצם פעולת שרשור מחרוזות ולכן הפלט שאני מקבל הוא:
Directory: app Owned By ynonp Permissions: drwxr-xr-x
Directory: bin Owned By ynonp Permissions: drwxr-xr-x
Directory: config Owned By ynonp Permissions: drwxr-xr-x
Directory: db Owned By ynonp Permissions: drwxr-xr-x
Directory: design Owned By ynonp Permissions: drwxr-xr-x
Directory: doc Owned By ynonp Permissions: drwxr-xr-x
Directory: engines Owned By ynonp Permissions: drwxr-xr-x
Directory: lib Owned By ynonp Permissions: drwxr-xr-x
Directory: log Owned By ynonp Permissions: drwxr-xr-x
Directory: public Owned By ynonp Permissions: drwxr-xr-x
Directory: storage Owned By ynonp Permissions: drwxr-xr-x
Directory: test Owned By ynonp Permissions: drwxr-xr-x
Directory: tmp Owned By ynonp Permissions: drwxr-xr-x
Directory: vendor Owned By ynonp Permissions: drwxr-xr-x
זה נתן לי הרבה אינפורמציה על התיקיה, אבל לפקודה הזו יש שתי בעיות: גם היה קשה לכתוב אותה, וגם - ויותר מעצבן - הפלט לא מסודר. בכל שורה המילים Owned By ו Permissions מתחילות במקום אחר לפי אורך שם הקובץ.
הפקודה printf, שאולי מוכרת לכם משפות תכנות אחרות כמו c או perl, מאפשרת להתמודד עם המצב הזה ולכתוב קוד awk שמדפיס דברים קצת יותר מתוחכמים. לדוגמה הפקודה:
$ ls -l | awk '/^d/ { printf "Directory: %-30s | Owned By: %-10s | Permissions: %-10s\n", $9, $3, $1 }'
מדפיסה לי את הפלט:
Directory: app | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: bin | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: config | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: db | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: design | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: doc | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: engines | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: lib | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: log | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: public | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: storage | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: test | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: tmp | Owned By: ynonp | Permissions: drwxr-xr-x
Directory: vendor | Owned By: ynonp | Permissions: drwxr-xr-x
למידע נוסף על printf אפשר להסתכל בדף התיעוד כאן:
https://alvinalexander.com/programming/printf-format-cheat-sheet/
ב awk פקודת printf לא מוסיפה ירידת שורה בצורה אוטומטית אחרי כל הדפסה, ולכן הוספתי בעצמי את הסימן \n בסוף הטקסט שאני מדפיס דרך printf.
## משתנים מיוחדים NF, NR, END, BEGIN1 419
# מדריך awk
הכלי awk היה לאורך שנים ועודנו אחד הכלים המרכזיים לעיבוד טקסט בסביבת יוניקס. הוא פותח בשנות ה 70 בחברת Bell Labs על ידי שלושה מפתחים ששמותיהם נתנו לכלי את שמו: Alfred Aho, Peter Weinberger ו Brian Kernighan. הייחודיות של awk היא שמצד אחד הוא כולל אלמנטים מתקדמים משפות תכנות כמו משתנים, מערכים, מילונים ואפילו אפשרות להגדיר פונקציות; אבל מצד שני זה כלי סקריפטים שהשימוש בו מאוד ממוקד לפיענוח טקסטים.
היכרות עם awk תאפשר לנו להעלות את הרמה של הסקריפטים שאנחנו כותבים וגם לפתור בעיות הקשורות לטקסט במהירות ומשורת הפקודה.
## מבנה פקודת awk
בלוק awk בסיסי מורכב משני חלקים: החלק הראשון נקרא תנאי או תבנית, והחלק השני נקרא פקודה. בהפעלה של awk בצורה אוטומטית awk יקרא את הקלט שורה אחרי שורה, ינסה להתאים כל שורה לתנאי שיש לו בפקודה ואם השורה מתאימה לתנאי awk יבצע את הפקודה.
תוכנית awk היא אוסף של בלוקים, וכש awk מקבל אוסף כזה הוא מנסה להתאים כל שורה בקלט לכל התנאים בכל הבלוקים, ומריץ את הפקודות שמתאימות.
בדוגמה ראשונה הבלוק הבא הוא קוד תקני ל awk:
// { print $1 }
החלק הראשון של הבלוק הוא התנאי - התנאי שכתבתי בדוגמה הוא הסימן //, שמסמן ביטוי רגולארי ריק. כל טקסט שנכתוב בין שני הלוכסנים ייקרא על ידי awk בתור ביטוי רגולארי, והתנאי שהוא מייצג הוא שהשורה מתאימה לאותו ביטוי. ביטוי רגולארי ריק מתאים לכל השורות.
החלק השני הוא הפקודה, ובדוגמה פקודה זו היא print $1. פקודת print מדפיסה משהו למסך. כש awk קורא שורה, על הדרך הוא גם מחלק את השורה למילים (כל מה שמופרד ברווח אחד או יותר), וכל מילה מקבלת מספר. הסימן $1 מייצג את המילה הראשונה בשורת הקלט.
דוגמאות נוספות שנוכל עכשיו לכתוב בבלוקים תוך שימוש בכלים שראינו יהיו-
1. הדפסת המילה הראשונה רק בשורות שמתחילות באות d:
/^d/ { print $1 }
2. הדפסת המילה השלישית בכל שורה שמכילה סיפרה:
/[0-9]/ { print $3 }
הסימן המיוחד $0 מייצג את כל השורה, ולכן הבלוק הבא ידפיס את כל השורות מהקלט שמתחילות באות גדולה:
/[A-Z]/ { print $0 }
## הפעלת awk
בשביל להפעיל awk אני יכול לכתוב את ה"תוכנית" שלו, כלומר את הבלוק או הבלוקים, בשורת הפקודה. זיכרו שבגלל שהרבה פעמים סימנים ש awk משתמש בהם הם כאלה שיש להם גם משמעות מיוחדת מבחינת ה shell, אנחנו רוצים "להגן" על הסימנים כדי שיגיעו ישירות ל awk ולא יפוענחו על ידי ה shell לפני הפעלת awk. מסיבה זו אני מציע להגן על תוכנית ה awk בגרש מכל צד.
הפקודה הבאה שכבר אפשר להפעיל משורת הפקודה מפעילה את ls -l, ומדפיסה את הבלוק הראשון (בלוק ההרשאות) רק לשורות שמתחילות באות d, כלומר רק לתיקיות:
$ ls -l | awk '/^d/ { print $1 }'
בשינוי פקודת ההדפסה אפשר לקבל מידע אחר או נוסף, לדוגמה הפקודה הבאה תדפיס עבור התיקיות גם את שם המשתמש שהן בבעלותו - בגלל ש ls -l מדפיסה את שם המשתמש בתור המילה השלישית בשורה:
$ ls -l | awk '/^d/ { print $1, $3 }'
## משתנים ב awk
בואו נתקדם ליכולות נוספות של awk - הראשונה היא משתנים. אנחנו משתמשים במשתנים כדי לשמור חישובים שצריכים קלט מכמה שורות, למשל יכול להיות שנרצה לספור כמה תיקיות יש בקלט. ב awk אני יכול פשוט להתחיל להשתמש במשתנה ולא צריך להגדיר אותו מראש. כל משתנה מתחיל עם הערך הריק - כאשר אם תשתמשו במשתנה בחישוב הערך הריק יהיה 0, ואם תשתמשו באותו משתנה בפעולה טקסטואלית (למשל הדפסה) הערך הריק יהיה מחרוזת ריקה.
נחזור לדוגמה שלנו וננסה לספור כמה תיקיות יש בפלט של ls -l. אני יודע שתיקיה היא שורה שמתחילה ב d ולכן אני יכול להוסיף 1 למשתנה כל פעם שמצאתי שורה שמתחילה ב d, ובסוף להדפיס את הערך שחישבתי. ניקח ניסיון ראשון ונפעיל:
$ ls -l | awk '/^d/ { count += 1 }'
אבל זה לא ממש עבד. הפלט הוא ריק.
אני יכול להוסיף פקודה נוספת לתנאי קיים על ידי שימוש בסימן ;. ננסה את זה:
$ ls -l | awk '/^d/ { count += 1; print count }'
ועכשיו הפלט אצלי בתיקיה הוא:
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
לא רע, אבל עדיין לא מה שרציתי. בגלל שההדפסה של count קורית כל פעם שמוצאים תיקיה אני מקבל הרבה הדפסות מיותרות. אני רק רוצה הדפסה אחת בסוף.
בשביל זה אני צריך להוסיף עוד בלוק ל awk, והפעם התנאי של הבלוק יהיה "הקלט הסתיים". ב awk המילה המיוחדת END מתארת את התנאי הזה בדיוק, ואני יכול לתת ל awk כמה בלוקים על ידי הפרדה שלהם ברווחים:1 419
# פרוטוקולים
לשים חגורה לפני שמתחילים לנסוע.
לצחצח שיניים לפני שהולכים לישון.
לבדוק מה מצב הזיכרון והדיסק כשמתחילים לחקור תקלה.
להסתכל בלוגים.
להוסיף הודעת הדפסה (או Breakpoint) במצב פיתוח.
לגבות את המחשב כל יום.
היום יום שלנו, ואיתו גם יום העבודה, מורכב מאינספור פעולות שאנחנו עושים "על אוטומט" פשוט כי ככה תמיד עשינו או כי מתישהו החלטנו שזה מועיל או כי מישהו החליט בשבילנו. וכן יש בנוסף גם קצת עבודה שדורשת חשיבה, קבלת החלטות וכל הדברים האלה.
דרך קלה לשפר פרודוקטיביות היא להתחיל לשים לב לפרוטוקולים האוטומטיים שלנו, לכתוב אותם ולתקן את אלה שכבר לא מתאימים לנו, או להוסיף אוטומציה לאלה שאנחנו יודעים שיישארו. אולי קשה יותר לראות אותם, אבל בסוף אלה הדברים שקובעים את התוצאה.
1 419
# היום למדתי: הפניית פלט לכמה קבצים ב zsh
לפעמים יתרונות קטנים מתגלים לאט. ל zsh עברתי כבר לפני כמה שנים כי אפל החליטו שזה shell ברירת מחדל טוב יותר ואף פעם לא היה לי אכפת במיוחד כי כל הטריקים שהכרתי מ bash עבדו טוב גם בו. והשמחה האמיתית היא להתחיל לגלות טריקים חדשים.
אנחנו כבר יודעים להשתמש ב tee כדי לכתוב בפקודה אחת לכמה קבצים:
$ echo hello world | tee a b c d >& /dev/null
וזה עובד גם ב bash וגם ב zsh. אבל מסתבר שב zsh יש עוד טריק איתו אפשר לוותר על tee:
$ { echo hello world } > a > b > c > d
וזה יוצר ארבעה קבצים שנקראים a, b, c ו d בהתאמה ובתוך כל אחד מהם כותב את הטקסט hello world.
מיטיבי לכת יכולים לוותר גם על כל החצים ולהשתמש בשתי נקודות כששמות הקבצים מתאימים. במקרה שלנו:
$ { echo hello world } > {a..d}
נ.ב. את הפקודה המרכזית אני מקיף בסוגריים מסולסלים כי אחרת zsh לא תמיד יודע שהיא נגמרה. הסבר מדויק אפשר למצוא בתיעוד שלהם כאן:
https://zsh.sourceforge.io/Doc/Release/Redirection.html#Multios1 419
# זקן מדי להתחיל לתכנת
המשפט "לעולם לא אהיה רוכב אופניים" יכול להביע אחד משני פחדים מאוד שונים:
1. פחד (קצת מוזר) של בן אדם שחושב שלא משנה כמה הוא ינסה, תמיד הוא ייפול מהאופניים.
2. פחד (יותר הגיוני) של בן אדם שמנסה לנצח באיזו תחרות אופניים, ומגלה שלא משנה כמה הוא משקיע תמיד יהיו רוכבים טובים ממנו והוא לעולם לא יצליח לנצח בשום תחרות.
יש הרבה סיבות בגללן יהיה קשה ואפילו בלתי אפשרי להיות רוכב אופניים מקצועי, או נגן כינור מקצועי, ספורטאי מקצועי, סופר מקצועי או שף מפורסם. במיוחד אם מתחילים בגיל מבוגר. אבל קשה לראות מצב שמישהו באמת מתאמן ולא מצליח לשמור על שיווי משקל באופניים, או לנגן שירי חג למשפחה, או לשחק כדורסל עם חברים או לבשל ארוחה למשפחה.
וכן גם לכתוב קוד.
לא בטוח שתתקבלו לגוגל או למייקרוסופט. לא בטוח שהמוצר שתבנו יזכה להצלחה מסחררת. לא בטוח שהקוד שלכם יזכה באיזושהי תחרות או שאי פעם תעברו את מבחני הקבלה הקריפטיים למוסד. וזה בסדר.
כן בטוח שאם תשבו מסודר, תלמדו ותתאמנו - בסופו של דבר תצליחו להגיד דברים בשפה שהמחשב יבין, אם זה בריאקט, ב JavaScript או בכל שפה אחרת. ולא משנה באיזה גיל מתחילים.
1 419
# תיקון ליל שבועות
חלק מכם, שמנויים לקבלת פוסטים חדשים דרך המייל, שמו לב וודאי לבאג שהיה במערכת בחג שבועות האחרון. בימים רגילים המערכת מקפידה לא לשלוח אימייל בחגים, אבל הפעם ערב חג שבועות יצא ביום שבת וזה בלבל את כל המנגנונים.
## מה קרה
המנגנון הרגיל של הפצת הפוסטים הוא בסך הכל סקריפט שמתעורר כל בוקר בשבע בבוקר ובתשע בערב בעזרת השורה הבאה ב crontab:
0 7,21 * * 0,1,2,3,4 /home/ynon/bin/cron/daily_blog.sh
ביום שישי הסקריפט מתעורר רק בשבע בבוקר כדי לא לשלוח מיילים בשבת:
0 7 * * 5 /home/ynon/bin/cron/daily_blog.sh
ובשבת הוא מתעורר רק בתשע בערב:
0 21 * * 6 /home/ynon/bin/cron/daily_blog.sh
ולמה ביום רגיל אנחנו מנסים פעמיים? כי חלק מהימים הרגילים הם חגים. בשביל לזהות חגים כתבתי את הסקריפט הקצר הבא שמשתמש ב hebcal:
evenings_ok=(
"Pesach I"
"Pesach VII"
"Shavuot I"
"Tish'a B'Av"
"Rosh Hashana II"
"Yom Kippur"
"Sukkot I"
"Shmini Atzeret"
)
no_emails=(
"Rosh Hashana"
)
for send_in_evening in "${evenings_ok[@]}"
do
# echo "^[0-9/]+ ${send_in_evening}"
if hebcal $(date +"%m %d %Y") | egrep -v "CH''M" | egrep "^[0-9/]+ ${send_in_evening}" >& /dev/null
then
if (( $(date +%"_H") < 20 ))
then
exit 1
fi
fi
done
for holiday in "${no_emails[@]}"
do
if hebcal $(date +"%m %d %Y") | egrep "^[0-9/]+ ${holiday}" >& /dev/null
then
exit 1
fi
done
הסקריפט מנסה לזהות אם אנחנו עכשיו באמצע חג באמצעות hebcal. הוא מפעיל hebcal, משתמש ברשימה של חגים שהוא מכיר, ואם hebcal מדפיס שאנחנו עכשיו באחד החגים האלה והשעות מתאימות הסקריפט יצא עם exit 1.
אפשר להפעיל את הסקריפט מכל שפת תכנות או אפילו משורת הפקודה:
$ source holiday_protect.sh && echo Not a holiday
ואם אנחנו לא בחג נקבל את ההדפסה.
## מה נשבר
בשבועות האחרון ערב חג יצא בדיוק ביום שבת - מה שאומר שבאותו יום לא רצינו לשלוח מייל גם בבוקר כי שבת, וגם בערב כי ערב חג. אבל ערבי חג לא קיבלו שום טיפול בסקריפט המקורי.
פה המקום לשאול - למה בעצם לא ראינו את התקלה הזאת קודם? למה המערכת לא שלחה קבוע מיילים בערבי חג? למה היה צריך לחכות דווקא לערב חג שיוצא בשבת?
התשובה היא שבאמת לא היה שום מנגנון שמדלג על שליחת אימייל בערב חג, אבל כן היה מנגנון ששולח את המייל בערב החג בשבע בבוקר. לכן כשהגיע תשע בערב באותו יום של ערב חג, המערכת רצתה לשלוח את הפוסט החדש במייל אבל פשוט לא היה לה למי. כולם כבר קיבלו את הפוסט בבוקר.
(וכן אנחנו עכשיו רואים שאני גם זוכר למי שלחתי איזה אימייל כדי לא לשלוח פעמיים, אבל זה כבר מנגנון אחר).
## התיקון
בחזרה לשבועות - באותו יום בגלל שערב חג יצא בשבת אז בבוקר לא נשלח המייל לאף אחד. המערכת התעוררה במוצאי שבת, גילתה שאין בעיה לשלוח, כי אף פעם לא בדקנו ערבי חג, ושלחה את הפוסט היומי להפתעתם של המנויים במייל.
אחד המופתעים, שמחה בונם חמניצקי, גם הציע עזרה ובנה גירסה מתוקנת לסקריפט שמזהה ערבי חג. אם במקרה אתם גם מחפשים פיתרון לזהות חגים ושבתות כדי לעצור בהם פעולות אוטומטיות אז הנה הסקריפט המתוקן:
evenings_ok=(
"Pesach I"
"Pesach VII"
"Shavuot I"
"Tish'a B'Av"
"Rosh Hashana II"
"Yom Kippur"
"Sukkot I"
"Shmini Atzeret"
)
no_emails=(
"Rosh Hashana"
)
no_emails_erev=(
"Erev Pesach"
"Erev Shavuot"
"Erev Rosh Hashana"
"Erev Sukkot"
"Erev Yom Kippur"
)
for send_in_evening in "${evenings_ok[@]}"
do
# echo "^[0-9/]+ ${send_in_evening}"
if hebcal $(date +"%m %d %Y") | egrep -v "CH''M" | egrep "^[0-9/]+ ${send_in_evening}" >& /dev/null
then
if (( $(date +%"_H") < 20 ))
then
exit 1
fi
fi
done
for holiday in "${no_emails[@]}"
do
if hebcal $(date +"%m %d %Y") | egrep "^[0-9/]+ ${holiday}" >& /dev/null
then
exit 1
fi
done
for erev_holiday in "${no_emails_erev[@]}"
do
if [hebcal $(date +"%m %d %Y") | egrep "^[0-9/]+ ${erev_holiday}" >& /dev/nul] && [$(date +"_H") > 19]
then
exit 1
fi
done
¡Ya disponible! Investigación de Telegram 2025 — los principales insights del año 
