Bash Tips
מילונים / מערכים אסוציאטיבים ובאש מאז הגרסאות האחרונות של באש יש אובייקט חדש שמוכר מכל שפת תכנות נפוצה הלא הוא מילון/מערך אסוציטיבי, ניתן להגדיר אותו על ידי שימוש ב declare שדיברנו עליו בעבר $ declare -A my_dict $ my_dict=(["First Name"]="Yossi" ["Last Name"]="Cohen")…
מפתחות וערכים
רמת קושי: #advanced
כפי שראינו בעבר הגדרה של מערך נראית כך
ישנם משתנים בבאש שמכילים מידע במערך אסוציאטיבי, למשל המשתנה הגלובלי BASH_ALIASES שמכיל את כל האליאסים בתצורה של key value
נ.ב. ימים אחרונים לאתגר ולאחר מכן אפרסם פתרון אפשרי.
#array
@bash_tips
רמת קושי: #advanced
כפי שראינו בעבר הגדרה של מערך נראית כך
$ declare -A my_array
$ my_array=([first]="test here" [second]="more text here")
יש לנו מערך אסוציאטיבי בעל שני מפתחות, בכדי לקבל ערך פשוט קוראים למערך עם המפתח המתאים.$ echo "${my_array[first]}"
test here
$ echo "${my_array[second]}"
more text here
אפשרות נוספת שבאש מאפשרת לנו היא לרוץ רק על הערכים על ידי שימוש ב @
במקום מפתח $ for i in "${my_array[@]}"; do
echo $i
done
test here
more text here
או אם רוצים לקבל רק את המפתחות פשוט לשים !
לפני שם המערך$ for i in "${!my_array[@]}"; do
echo $i
done
first
second
ובדומה למערך רגיל #
לפני שם המערך האסוציאטיבי יחזיר את הגודל שלו $ echo "${#my_array[@]}"
2
למה זה מעניין אותנו כל הסיפור הזה?ישנם משתנים בבאש שמכילים מידע במערך אסוציאטיבי, למשל המשתנה הגלובלי BASH_ALIASES שמכיל את כל האליאסים בתצורה של key value
נ.ב. ימים אחרונים לאתגר ולאחר מכן אפרסם פתרון אפשרי.
#array
@bash_tips
מניפולציה על מערכים
רמת קושי: #advanced
דברנו בעבר על כך שבאש מאפשרת לבצע מניפולציה על משתנים, להגדיל אותיות או להקטין, להחליף תוכן בתוכן אחר וכו'
מסתבר שאת כל הפעולות הללו ניתן לבצע גם על מערך שלם, מבלי לרוץ עליו בלולאה.
לדוגמה, להלן מערך של משתמשים שכל איבר בו מכיל שם משתמש וסיסמה, פעם אחת אנו רוצים לקבל את המשתמשים ופעם אחרת רק את הסיסמאות
אם נמשיך עם הרעיון הזה נראה שאפשר לממש מילון עם מפתח מכל סוג שנרצה, על ידי כך שנעביר מחרוזת וכשנרצה לשלוף אותה, נבצע מניפולציה על התוכן
#array
#parameter_expansion
@bash_tips
רמת קושי: #advanced
דברנו בעבר על כך שבאש מאפשרת לבצע מניפולציה על משתנים, להגדיל אותיות או להקטין, להחליף תוכן בתוכן אחר וכו'
$ myvar=my_username:my_password
$ echo ${myvar^^}
MY_USERNAME:MY_PASSWORD
$ echo ${myvar//my/your}
your_username:your_password
מסתבר שאת כל הפעולות הללו ניתן לבצע גם על מערך שלם, מבלי לרוץ עליו בלולאה.
לדוגמה, להלן מערך של משתמשים שכל איבר בו מכיל שם משתמש וסיסמה, פעם אחת אנו רוצים לקבל את המשתמשים ופעם אחרת רק את הסיסמאות
$ users_auth=(admin:1234356)
$ users_auth+=(myuser:456789)
$ users_auth+=(writer:00001)
$ echo ${users_auth[@]}
admin:1234356 myuser:456789 writer:00001
$ echo ${users_auth[@]/:*}
admin myuser writer
$ echo ${users_auth[@]/*:}
1234356 456789 00001
אם נמשיך עם הרעיון הזה נראה שאפשר לממש מילון עם מפתח מכל סוג שנרצה, על ידי כך שנעביר מחרוזת וכשנרצה לשלוף אותה, נבצע מניפולציה על התוכן
#array
#parameter_expansion
@bash_tips
להמיר מחרוזת למערך
רמת קושי: #beginners
כפי שאנו מכירים יצירת מערך בבאש נעשית על ידי הכנסת ערכים לסוגריים עגולים
קוד שאפשר לראות לא מעט עושה את הדבר הבא כדי לקבל מערך ממחרוזת
דרך נוספת וקריאה יותר היא להעביר את המשנה כמות שהוא בלי גרשיים
בעוד שבדרך כלל כל התייחסות למשתנה צריכה להיות כשהוא עטוף בגרשיים, במקרה שלנו דווקא החוסר שלהם עושה את הקסם.
קריאה למשתנה "מגורגש"
במידה והמחרוזת שלנו מכילה ערכים שהם globbing characters מה שיקרה הוא שבאש יתייחס לתווים הללו כבעלי משמעות ולא יתייחס אליהם כתווים רגילים.
כפי שניתן לראות חוץ מהמחרוזת שלנו קיבלנו גם את רשימת הקבצים שבתיקייה, כל זה קורה כי התוכן שאנו מעבירים למערך הוא לא רק מחרוזת אלא מחרוזת וביטוי wildcard ולכן בנוסף לתוכן שהעברנו קיבלנו גם את רשימת הקבצים בתיקייה ממנה הרצנו את הפקודה.
#globbing_characters
#declare
#array
@bash_tips
רמת קושי: #beginners
כפי שאנו מכירים יצירת מערך בבאש נעשית על ידי הכנסת ערכים לסוגריים עגולים
$ arr=(1 2 3)
$ declare -p arr
declare -a arr=([0]="1" [1]="2" [2]="3")
קריאה למשתנה בתוך הסוגרייםקוד שאפשר לראות לא מעט עושה את הדבר הבא כדי לקבל מערך ממחרוזת
$ numbers="1 2 3"
$ myarr=($(echo "${numbers}"))
האמת זה עובד נפלא, shellcheck לא ממש אוהב את זה, אבל זהו קוד שעובד.דרך נוספת וקריאה יותר היא להעביר את המשנה כמות שהוא בלי גרשיים
בעוד שבדרך כלל כל התייחסות למשתנה צריכה להיות כשהוא עטוף בגרשיים, במקרה שלנו דווקא החוסר שלהם עושה את הקסם.
קריאה למשתנה "מגורגש"
$ numbers="1 2 3"
$ myarr=("${numbers}")
$ declare -p myarr
declare -a myarr=([0]="1 2 3")
קריאה למשתנה נטול גרשיים$ myarr=(${numbers})
$ declare -p myarr
declare -a myarr=([0]="1" [1]="2" [2]="3")
היכן הפינות?במידה והמחרוזת שלנו מכילה ערכים שהם globbing characters מה שיקרה הוא שבאש יתייחס לתווים הללו כבעלי משמעות ולא יתייחס אליהם כתווים רגילים.
$ ls
1.txt 2.txt 3.txt
$ myvar="text with wildcard *"
$ mylist=( $(echo "$myvar") )
$ declare -p mylist
declare -a mylist=([0]="text" [1]="with" [2]="wildcard" [3]="1.txt" [4]="2.txt" [5]="3.txt")
כפי שניתן לראות חוץ מהמחרוזת שלנו קיבלנו גם את רשימת הקבצים שבתיקייה, כל זה קורה כי התוכן שאנו מעבירים למערך הוא לא רק מחרוזת אלא מחרוזת וביטוי wildcard ולכן בנוסף לתוכן שהעברנו קיבלנו גם את רשימת הקבצים בתיקייה ממנה הרצנו את הפקודה.
#globbing_characters
#declare
#array
@bash_tips
מפות ומערכים
רמת קושי: #beginners
בעבר דיברנו על מערכים וההפתעות שאנו עשויים להיתקל בהן, ישנם מספר דרכים ליצור מערכים בצורה בטוחה, זוהי הדרך ליצור מערך של שורות מקובץ .
פקודת
כברירת מחדל הפקודה טוענת את התוכן שלה למשתנה סביבה בשם
במידה ונעביר שם של משתנה התוכן יכניס למשתנה אותו העברנו
כפי שניתן להעביר קובץ, ניתן גם להעביר תוכן של משתנה למערך על ידי שימוש ב here-string
#array
#mapfile
#readarray
@bash_tips
רמת קושי: #beginners
בעבר דיברנו על מערכים וההפתעות שאנו עשויים להיתקל בהן, ישנם מספר דרכים ליצור מערכים בצורה בטוחה, זוהי הדרך ליצור מערך של שורות מקובץ .
פקודת
mapfile
והמקבילה שלה בבאש readarray
, הן בעצם אותו כלי שכל תפקידו זה לקבל תוכן מה stdin וליצור מערך לפי מספר השורות שיש בתוכן.כברירת מחדל הפקודה טוענת את התוכן שלה למשתנה סביבה בשם
MAPFILE
וצורת הפעולה שלה נראית כך.
$ mapfile < myfile
$ echo ${MAPFILE[@]}
one
two
three
במידה ונעביר שם של משתנה התוכן יכניס למשתנה אותו העברנו
$ mapfile mylist < myfile
$ echo ${mylist[@]}
one
two
three
כפי שניתן להעביר קובץ, ניתן גם להעביר תוכן של משתנה למערך על ידי שימוש ב here-string
$ mapfile mylist <<< "$myvar"
$ echo ${mylist[@]}
one
two
three
#array
#mapfile
#readarray
@bash_tips
מערכים ומפות read
רמת קושי: #beginners
בפוסט הקודם דיברנו על פקודת mapfile / readarray ואיך יהיה נכון יותר ליצור מערך של שורות מקובץ מבלי לבצע פעולות שלא אליהן התכוונו, מה אם מה שאנו צריכים זה ליצור מערך של מילים משורה בודדת?
את פקודת read ראינו בפוסטים קודמים, הפקודה מאפשרת לקבל ערכים מה stdin למשתנה, בצורתה הפשוטה הפקודה מעבירה את הקלט כמחרוזת, עם זאת ניתן לקבל את הקלט כמערך על ידי שימוש בפרמטר -a
חשוב לשים לב, הפקודה תיקח רק את השורה הראשונה שהועברה אליה ותפצל אותה למערך, ולכן במידה ומעבירים מחרוזת של מספר שורות כדאי להתשמש ב read while כפי שראינו בעבר, ולפצל את השורה כפי שראינו זה עתה.
#read
#array
@bash_tips
רמת קושי: #beginners
בפוסט הקודם דיברנו על פקודת mapfile / readarray ואיך יהיה נכון יותר ליצור מערך של שורות מקובץ מבלי לבצע פעולות שלא אליהן התכוונו, מה אם מה שאנו צריכים זה ליצור מערך של מילים משורה בודדת?
את פקודת read ראינו בפוסטים קודמים, הפקודה מאפשרת לקבל ערכים מה stdin למשתנה, בצורתה הפשוטה הפקודה מעבירה את הקלט כמחרוזת, עם זאת ניתן לקבל את הקלט כמערך על ידי שימוש בפרמטר -a
$ myline="one two three"
$ read -a mylist <<< "${myline}"
$ declare -p mylist
declare -a mylist='([0]="one" [1]="two" [2]="three")'
חשוב לשים לב, הפקודה תיקח רק את השורה הראשונה שהועברה אליה ותפצל אותה למערך, ולכן במידה ומעבירים מחרוזת של מספר שורות כדאי להתשמש ב read while כפי שראינו בעבר, ולפצל את השורה כפי שראינו זה עתה.
#read
#array
@bash_tips
להעביר מערכים לפונקציה
רמת קושי: #advanced
המכניקה של באש בהעברת ערכים היא שכל ערך שמגיע לאחר שם הפונקציה מאוחסן במשתנה, כשרווח הוא זה שמפצל בין הארגומנטים.
מעבר לאפשרות של משתנים גלובלים להלן 2 דרכים שונות להתמודד עם הבעיה
פתרון 1: העברת מחרוזות
הפתרון הראשון הוא פשוט להעביר את המערך כמחרוזת ואז בפונקציה עצמה ליצור מערך חדש על ידי העברה של המחרוזת ל read כפי שראינו בעבר.
בגלל שread מפצל את המחרוזת לפי רווחים, מה יקרה אם אחד האיברים יכיל רווח?
מה שיקרה הוא שאותו איבר יהפוך לשני איברים שונים, לא הדבר שהיינו רוצים שיקרה.
פתרון 2: indirect references
את
יש לנו 2 מערכים, לפונקציה אנו מעבירים את השמות של המערכים מבלי לקרוא להם, בפונקציה עצמה אנו "מחוללים" את שמות המערכים כדי לקבל את הערך שלהם, את כל זה אנו עושים בתוך סוגריים כדי ליצור מהערך שאנו מקבלים מערך.
#array
#function
#arguments
#indirect_references
@bash_tips
רמת קושי: #advanced
המכניקה של באש בהעברת ערכים היא שכל ערך שמגיע לאחר שם הפונקציה מאוחסן במשתנה, כשרווח הוא זה שמפצל בין הארגומנטים.
$ cat test.sh
myFunc(){
echo $1
echo $2
}
myFunc one two
$ bash test.sh
one
two
מה יקרה אם נרצה להעביר מערך לפונקציה? אם ננסה להריץ את הקוד הבא נגלה שבעצם האיבר הראשון הולך למשתנה הראשון בפונקציה והאיבר השני הולך למשתנה השני וכן הלאה myFunc(){
echo $1
echo $2
}
myList=(one two)
myFunc ${myList[@]}
$ bash test.sh
one
two
נוכל לנסות לאסוף את כל הערכים שמגיעים לפונקציה ולהגדיר אותם כמערך על ידי שימוש ב ${@}
בפונקציה, אבל זה יכול לעבוד רק בתנאי שאין לנו עוד ערכים אחרים להעביר לפונקציהmyFunc(){
echo ${@}
echo $2
}
myList=(one two)
myFunc ${myList[@]} new_parameter
$ bash test.sh
one two new_parameter
two
בעיה, איך בעצם כן אפשר להעביר מערכים לפונקציות בבאש?מעבר לאפשרות של משתנים גלובלים להלן 2 דרכים שונות להתמודד עם הבעיה
פתרון 1: העברת מחרוזות
הפתרון הראשון הוא פשוט להעביר את המערך כמחרוזת ואז בפונקציה עצמה ליצור מערך חדש על ידי העברה של המחרוזת ל read כפי שראינו בעבר.
myFunc(){
read -r -a arr1 <<< ${1}
read -r -a arr2 <<< ${2}
declare -p arr1 arr2
}
arr1=(aaa bbb ccc)
arr2=(ddd eee fff)
myFunc "${arr1[*]}" "${arr2[*]}"
$ bash test.sh
declare -a arr1=([0]="aaa" [1]="bbb" [2]="ccc")
declare -a arr2=([0]="ddd" [1]="eee" [2]="fff")
אחלה נראה שעובד נהדר, היכן הבעיה?בגלל שread מפצל את המחרוזת לפי רווחים, מה יקרה אם אחד האיברים יכיל רווח?
מה שיקרה הוא שאותו איבר יהפוך לשני איברים שונים, לא הדבר שהיינו רוצים שיקרה.
פתרון 2: indirect references
את
indirect references
הכרנו בפוסט הקודם, בגדול מה שזה אומר הוא שניתן לקבל איזה ערך של משתנה שנרצה מאיזה חלק בתוכנית על ידי חיפוש שם המשתנה בזיכרון התוכנית$ cat test.sh
myFunc(){
func_arr1=( "${!1}" )
func_arr2=( "${!2}" )
declare -p func_arr1 func_arr2
}
arr1=("aaa" "bbb space" "ccc")
arr2=("ddd" "eee" "fff")
myFunc arr1[@] arr2[@]
אז מה היה לנו פה?יש לנו 2 מערכים, לפונקציה אנו מעבירים את השמות של המערכים מבלי לקרוא להם, בפונקציה עצמה אנו "מחוללים" את שמות המערכים כדי לקבל את הערך שלהם, את כל זה אנו עושים בתוך סוגריים כדי ליצור מהערך שאנו מקבלים מערך.
$ bash test.sh
declare -a func_arr1=([0]="aaa" [1]="bbb space" [2]="ccc")
declare -a func_arr2=([0]="ddd" [1]="eee" [2]="fff")
#read#array
#function
#arguments
#indirect_references
@bash_tips