תחי ישראל - אין לנו ארץ אחרת

תחי ישראל -אין לנו ארץ אחרת

מדריך Awk - פקודה של Bash שהיא גם (סוג של) שפת תכנות

מחבר:
בתאריך:

Awk היא פקודה של Bash וגם שפת תכנות. נעזרים בה בעיקר כשעובדים עם עמודות.

מדריך Awk של bash

נוסיף את הקובץ "cars.text":

$ touch cars.text

בתוכו נכתוב מידע המכיל עמודות:

BMW i7 1250000
jaguar F-PACE 380000
mercedes CL-Class 370000
tesla Model3 216000
ferrari 812GTS 2300000
maserati Grecale 440000

נדפיס את העמודה הראשונה:

$ awk '{print $1}' cars.text

התוצאה:

BMW
jaguar
mercedes
tesla
ferrari
maserati
  • עבור awk רווחים מפרידים בין העמודות.

השורות במלואן מאוחסנות בתוך המשתנה $0 וניתן לגשת לכל אחת מהעמודות על פי מספר: $1 לעמודה הראשונה, $2 לשנייה וכיו"ב. העמודה האחרונה מאוחסנת בתוך המשתנה $NF.

נדפיס את העמודה השנייה:

$ awk '{print $2}' cars.text

התוצאה:

i7
F-PACE
CL-Class
Model3
812GTS
Grecale

אפשר להדפיס יותר מעמודה אחת:

$ awk '{print $1 $2 $3}' cars.text

התוצאה:

BMWi71250000
jaguarF-PACE380000
mercedesCL-Class370000
teslaModel3216000
ferrari812GTS2300000
maseratiGrecale440000
  • התוצאה הודפסה ללא רווחים בין העמודות.

נוסיף רווח של תו אחד בין העמודות כדי לשפר את המראה:

$ awk '{print $1" "$2" "$3}' cars.text

התוצאה:

BMW i7 1250000
jaguar F-PACE 380000
mercedes CL-Class 370000
tesla Model3 216000
ferrari 812GTS 2300000
maserati Grecale 440000

אפשר לרווח עוד יותר ע"י הוספת tab בין העמודות:

awk use tabs to separate between columns

התוצאה:

BMW	i7	1250000
jaguar	F-PACE	380000
mercedes	CL-Class	370000
tesla	Model3	216000
ferrari	812GTS	2300000
maserati	Grecale	440000
  • בתחביר זה אפשר להשתמש בכל תו בתור מפריד בין העמודות שיוצג בפלט.

לא תמיד ההפרדה בין העמודות היא באמצעות רווח. לדוגמה, הקובץ /etc/passwd משתמש בנקודתיים (:).

נערוך את התוכן של הקובץ שלנו "cars.text" כדי שנקודתיים יפרידו בין העמודות:

BMW:i7:1250000
jaguar:F-PACE:380000
mercedes:CL-Class:370000
tesla:Model3:216000
ferrari:812GTS:2300000
maserati:Grecale:440000

בגלל שברירת המחדל של awk למפריד בין עמודות הוא רווח נעביר לפקודה הוראה למפריד אחר. נקודתיים כי זה מה שמפריד בין העמודות בקובץ שלנו:

$ awk -F ':' '{print $1}' cars.text
  • העברנו לאופציה F את התו ":" כדי שזה ישמש כמפריד בין השדות

התוצאה:

BMW
jaguar
mercedes
tesla
ferrari
maserati

נשתמש ב-$NF כדי להדפיס את העמודה האחרונה בכל שורה:

$ awk -F ':' '{print $NF}' cars.text

התוצאה:

1250000
380000
370000
216000
2300000
440000

 

שימוש בביטויים ב-Awk

עוד אפשרות שימושית במיוחד היא לסנן שורות על בסיס מחרוזות המופיעות בהם.

לדוגמה, נדפיס שורות רק בתנאי שהן מכילות את הביטוי "tesla":

$ awk '/tesla/ {print $0}' cars.text

התוצאה:

tesla:Model3:216000
  • מגדירים את הביטוי שמחפשים בין סלאשים. במקרה זה "/tesla/"
  • השתמשנו ב-$0 כדי להדפיס את כל אורך השורה

הביטוי לא חייב להיות מדויק. אנחנו יכולים להשתמש בביטוי רגולרי. לדוגמה, להדפיס רק את השורות הכוללות את הביטוי "class" בלי להתחשב האם האותיות הם גדולות או קטנות:

$ awk 'tolower($0) ~ /class/ {print $0}' cars.text
  • $0 כדי לקבל את כל השורה
  • הפקודה tolower הופכת את הטקסט לאותיות קטנות
  • הביטוי הרגולרי מחפש התאמה ל-"class"

התוצאה:

mercedes:CL-Class:370000

הנושא של ביטויים רגולריים הוא רחב ומעניין מאוד. סקרתי אותו בהרחבה במדריך ביטויים רגולריים בפייתון. במדריך זה אציג טעימה קלה מהיכולת של ביטויים אילה שהשימוש בהם מסייע רבות כשעובדים עם מחרוזות.

אפשרות נוספת לשימוש בביטויים רגולריים היא לבחור בין כמה אפשרויות בתוך סוגריים כאשר החלופות מופרדות בפייפים. במקרה זה, נבחר בשורות המכילות את הביטוי "model" או "class":

$ awk 'tolower($0) ~ /(class|model)/ {print $0}' cars.text

התוצאה:

mercedes:CL-Class:370000
tesla:Model3:216000

אנחנו יכולים להשתמש במפריד שאינו רווח להפרדה בין עמודות וגם להגדיר שבתוצא (output) יוצג מפריד אחר.

לדוגמה, נהפוך את המפריד המקורי (:) בקלט למפריד טאב בפלט:

use awk options FS and OFS to set the input and output separators

  • המשתנה FS מפריד את השורות לעמודות בקלט באמצעות ביטוי רגולרי (שהוא במקרה שלנו :)
  • המשתנה OFS קובע מה יהיה המפריד בין העמודות בפלט.

התוצאה:

BMW	i7	1250000
jaguar	F-PACE	380000
mercedes	CL-Class	370000
tesla	Model3	216000
ferrari	812GTS	2300000
maserati	Grecale	440000

בוא נראה דוגמה מהחיים מקובץ /etc/shells המכילה את רשימת ה-shells (שורת הפקודות שאנחנו רואים בטרמינל דוגמת bash).

נציג את תוכן הקובץ בטרמינל באמצעות הפקודה:

$ cat /etc/shells

כך זה נראה אצלי:

# /etc/shells: valid login shells
/bin/sh
/bin/bash
/usr/bin/bash
/bin/rbash
/usr/bin/rbash
/bin/dash
/usr/bin/dash
/usr/bin/sh
  • הנתיב המלא של כל ה-shells

במקום הנתיב המלא ניקח רק את החלק האחרון בכל שורה באמצעות $NF:

awk code regular expression and escape slash

  • שימוש בסלאש "/" בתור המפריד בין השדות (במקום ברווח)
  • הביטוי הרגולרי תחום בין שני סלאשים //
  • בתוך הביטוי הסמל "^" מציין את תחילת השורה
  • חייבים לעשות אסקייפ של הסלאש כדי ש-bash לא יתבלבל בין הסלאשים

התוצאה:

sh
bash
bash
rbash
rbash
dash
dash
sh
  • בין השאר קיבלנו תוצאות כפולות. לדוגמה, פעמיים bash ופעמיים sh.

נעשה פייפ לפקודה uniq כדי לקבל תוצאות ייחודיות:

awk code with the uniq filter

התוצאה:

sh
bash
rbash
dash

את הנושא של פייפים והפניות הסברתי במדריך "לינוקס - צינורות והפניות".

נסדר את התוצאות בסדר עולה באמצעות פייפ לפקודה sort:

awk code with the uniq & sort filters

התוצאה:

bash
dash
rbash
sh

 

Awk גם על פקודות

עד כה עבדנו עם awk על קבצים עם התחביר:

awk '{...}' /path/to/file

ניתן לעשות פייפ של פקודה ולהעביר ל-awk עם תחביר:

[command] | awk '{...}'

הפקודה df של לינוקס מציג את כמות המקום החופשי במערכת הקבצים:

$ df

כך זה נראה על המחשב שאני עובד עליו כרגע:

Filesystem     1K-blocks      Used Available Use% Mounted on
tmpfs            1625308      3764   1621544   1% /run
/dev/sda3      459848776 299298232 137117996  69% /
tmpfs            8126528    257180   7869348   4% /dev/shm
tmpfs               5120         4      5116   1% /run/lock
/dev/sda2         524272      5368    518904   2% /boot/efi
tmpfs            1625304      2444   1622860   1% /run/user/1000

נעשה לפקודה df פייפ ל-awk ונציג רק את 3 העמודות הראשונות של השורות המכילות את הביטוי "dev/sda":

pipe df to awk command

  • כפי שראינו בדוגמה קודמת, צריך לעשות אסקייפ לסלאש כדי שיעבוד.

התוצאה:

/dev/sda3	459848776	299300336
/dev/sda2	524272	5368

 

שימוש ב-Awk כשפת סקריפט

Awk היא שפת סקריפט ולכן, לדוגמה, אפשר לבצע פעולות חשבוניות על עמודות.

לחבר בין שתי עמודות:

use awk to add quantities

התוצאה אצלי:

/dev/sda3	759147764
/dev/sda2	529640

לסכום את העמודה השנייה:

$ df | awk '{s+=$2} END {print s}'

 

נשתמש בפקודה length כדי לקבל את אורך השורות:

$ awk '{print length($0)}' cars.text
  • כל מה שקיים בשורה מאוחסן בתוך המשתנה $0
  • הפונקציה length() מחזירה את אורך המחרוזת, מספר התווים

התוצאה:

14
20
24
19
22
23

נדפיס את השורות בתנאי שמספר התווים גדול מ-20:

$ awk 'length($0) > 20' cars.text

התוצאה:

mercedes:CL-Class:370000
ferrari:812GTS:2300000
maserati:Grecale:440000

 

הפקודה ps משמשת להצגת כל התהליכים של המשתמש. נריץ אותה:

$ ps -ef

נבחר מתוך התהליכים במערכת רק את אילו השייכים לתהליך "mysqld" במדויק:

$ ps -ef | awk '{ if($NF == "/usr/sbin/mysqld") print $0}'

לא תמיד אנחנו יודעים את הנתיב המלא של תהליך. במקרים אילה ביטוי רגולרי יכול לפתור את הבעיה.

נעזר בביטוי רגולרי כדי למצוא את כל התהליכים שהנתיב בהם כולל את הביטוי "mysql":

$ ps -ef | awk '{ if($NF ~ /mysql/) print $0}'

 

נחזור לקובץ "cars.text".

כמה שורות בקובץ?

$ awk 'END {print NR}' cars.text

התוצאה:

6

נציץ ב-5 השורות הראשונות של הקובץ:

$ awk 'NR < 6' cars.text

התוצאה:

BMW:i7:1250000
jaguar:F-PACE:380000
mercedes:CL-Class:370000
tesla:Model3:216000
ferrari:812GTS:2300000

נוסיף מספור לשורות:

$ awk 'NR < 10 {print NR, $0}' cars.text

התוצאה:

1 BMW:i7:1250000
2 jaguar:F-PACE:380000
3 mercedes:CL-Class:370000
4 tesla:Model3:216000
5 ferrari:812GTS:2300000
6 maserati:Grecale:440000

נדפיס את השורה השלישית בלבד:

$ awk 'NR==3' cars.text

התוצאה:

mercedes:CL-Class:370000

נדפיס את השורות 2 עד 4:

$ awk 'NR==2, NR==4' cars.text

התוצאה:

jaguar:F-PACE:380000
mercedes:CL-Class:370000
tesla:Model3:216000

נדפיס כל שורה שנייה החל מהשורה הראשונה:

$ awk 'NR%2==1' cars.text

התוצאה:

BMW:i7:1250000
mercedes:CL-Class:370000
ferrari:812GTS:2300000

זה יכול לעבוד עם כל שורה שלישית:

$ awk 'NR%3==1' cars.text

נדפיס את כל השורות בהם העמודה השלישית היא בין הערכים 400,000 עד 2,000,000:

$ awk -F ":" '($3 >= 400000 && $3 <= 2000000)' cars.text

התוצאה:

BMW:i7:1250000
maserati:Grecale:440000

 

ניתן לבחור תת מחרוזת בעזרת הפונקציה substring() בתחביר:

substr(s, i, n)
  • מחזיר n תווים ממחרוזת s החל מעמדה i
  • הפרמטר n אינו חובה

נבחן את הפונקציה על המחרוזת אותה נדפיס למסך:

$ echo "I love awk"

נשתמש בפייפ כדי להעביר ל-Awk לצורך הפעלת הפונקציה substr() וקבלת חלקי המחרוזת שמעניינים אותנו:

$ echo "I love awk" | awk '{print substr($0,1,1)}'

נקרא את הפרמטרים משמאל לימין:

  • $0 מציין שאנחנו רוצים את כל המחרוזת
  • 1 כי אנו רוצים להתחיל מהעמדה הראשונה
  • 1 כי אנו רוצים להחזיר תו יחיד

התוצאה:

I

נתחיל להדפיס מהעמדה הרביעית ועד לסוף השורה:

$ echo "I love awk" | awk '{print substr($0,3)}'

התוצאה:

ove awk

 

Awk היא שפת סקריפט. ואכן, אפשר להריץ אפילו לולאות.

לולאת for:

$ awk 'BEGIN { for (i = 0; i <= 10; ++i) {print "index is: ", i} }'
  • בין הסוגריים המסולסלים אנחנו כותבים את הקוד לביצוע.

התוצאה:

index is:  0
index is:  1
index is:  2
index is:  3
index is:  4
index is:  5
index is:  6
index is:  7
index is:  8
index is:  9
index is:  10

נכתוב יותר מפקודה אחת בין הסוגריים המסולסלים:

$ awk 'BEGIN { for (i = 1; i <= 10; ++i) {print i; if (i%3==0) print "Aleerie"}}'

התוצאה:

1
2
3
Aleerie
4
5
6
Aleerie
7
8
9
Aleerie
10

לולאת while:

$ awk 'BEGIN { i = 1; while (i <= 10)  {print "random # ", i, " is ", int(rand()*100) ; i++} }'
  • הלולאה מדפיסה ערכים רנדומליים באמצעות הפונקציה rand() של Awk
  • הערכים הרנדומליים הם שברים עשרוניים בין 0 ל-1. הכפלה ב-100 והפיכה למספר שלם באמצעות הפונקציה int() מאפשרים לקבל מספרים בטווח שבין 0 ל-100.

התוצאה אצלי:

random #  1  is  38
random #  2  is  56
random #  3  is  34
random #  4  is  72
random #  5  is  83
random #  6  is  46
random #  7  is  59
random #  8  is  21
random #  9  is  37
random #  10  is  54

 

יש עוד הרבה מה ללמוד ולעשות. ממליץ מאוד להריץ את הפקודה man, ולקרוא את התיעוד הרשמי:

$ man awk

 

מדריכים נוספים בנושא לינוקס

חיפוש מחרוזות עם GREP

לינוקס - צינורות והפניות

סקריפט ראשון בשפת bash - שלום עולם כמובן!

 

לכל המדריכים בסדרת הלינוקס

 

אהבתם? לא אהבתם? דרגו!

0 הצבעות, ממוצע 0 מתוך 5 כוכבים

 

 

המדריכים באתר עוסקים בנושאי תכנות ופיתוח אישי. הקוד שמוצג משמש להדגמה ולצרכי לימוד. התוכן והקוד המוצגים באתר נבדקו בקפידה ונמצאו תקינים. אבל ייתכן ששימוש במערכות שונות, דוגמת דפדפן או מערכת הפעלה שונה ולאור השינויים הטכנולוגיים התכופים בעולם שבו אנו חיים יגרום לתוצאות שונות מהמצופה. בכל מקרה, אין בעל האתר נושא באחריות לכל שיבוש או שימוש לא אחראי בתכנים הלימודיים באתר.

למרות האמור לעיל, ומתוך רצון טוב, אם נתקלת בקשיים ביישום הקוד באתר מפאת מה שנראה לך כשגיאה או כחוסר עקביות נא להשאיר תגובה עם פירוט הבעיה באזור התגובות בתחתית המדריכים. זה יכול לעזור למשתמשים אחרים שנתקלו באותה בעיה ואם אני רואה שהבעיה עקרונית אני עשוי לערוך התאמה במדריך או להסיר אותו כדי להימנע מהטעיית הציבור.

שימו לב! הסקריפטים במדריכים מיועדים למטרות לימוד בלבד. כשאתם עובדים על הפרויקטים שלכם אתם צריכים להשתמש בספריות וסביבות פיתוח מוכחות, מהירות ובטוחות.

המשתמש באתר צריך להיות מודע לכך שאם וכאשר הוא מפתח קוד בשביל פרויקט הוא חייב לשים לב ולהשתמש בסביבת הפיתוח המתאימה ביותר, הבטוחה ביותר, היעילה ביותר וכמובן שהוא צריך לבדוק את הקוד בהיבטים של יעילות ואבטחה. מי אמר שלהיות מפתח זו עבודה קלה ?

השימוש שלך באתר מהווה ראייה להסכמתך עם הכללים והתקנות שנוסחו בהסכם תנאי השימוש.

הוסף תגובה חדשה

 

 

ענה על השאלה הפשוטה הבאה כתנאי להוספת תגובה:

דג למים הוא כמו ציפור ל...?

 

תמונת המגיב

מורן בתאריך: 08.03.2023

מושלם. תודה רבה!