הדפסת פלט
נתחיל בהדגמת פקודה שכבר ראינו בשיעורים קודמים: echo תדפיס ארגומנט לפלט הסטנדרטי:
$ echo "Hello World!"
Hello World!
כעת נשתמש בהפניה לקובץ כדי לשלוח את הפלט לקובץ חדש בשם new_script:
$ echo 'echo "Hello World!"' > new_script
$ cat new_script
echo "Hello World!"
הקובץ new_script כעת מכיל את אותה הפקודה.
הפיכת סקריפט להרצה
כעת נבצע מספר צעדים להפוך את הקובץ לסקריפט שניתן להריץ. ניתן לנסות להריץ את הסקריפט כמו כל פקודה אחרת:
$ new_script
/bin/bash: new_script: command not found
שגיאה זו נובעת מכך ש-Linux מחפש את הפקודה בספריות שמוגדרות במשתנה PATH, אך הקובץ שלנו אינו נמצא שם.
הגדרת PATH
כאשר אנו מקלידים פקודה כמו ls, המערכת מחפשת אותה בספריות שמוגדרות במשתנה PATH. נוכל לראות את הערכים של המשתנה בעזרת הפקודה echo:
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
מאחר שהספרייה הנוכחית אינה נמצאת במשתנה PATH, המערכת לא תמצא את הקובץ שלנו. ניתן לפתור זאת על ידי קריאת הקובץ בסימון מיקום יחסי:
$ ./new_script
/bin/bash: ./new_script: Permission denied
כעת המערכת מזהה את הקובץ, אך אין לו הרשאות הרצה.
הוספת הרשאות הרצה
נבדוק את ההרשאות של הקובץ:
$ ls -l new_script
-rw-rw-r-- 1 user user 20 Apr 30 12:12 new_script
נראה כי חסרות הרשאות הרצה. נוסיף אותן:
$ chmod +x new_script
$ ls -l new_script
-rwxrwxr-x 1 user user 20 Apr 30 12:12 new_script
כעת הקובץ ניתן להרצה:
$ ./new_script
Hello World!
הגדרת פרשן
למרות שהצלחנו להריץ את הסקריפט שלנו ב-Bash, חשוב להגדיר פרשן בעזרת שורת shebang בקובץ הסקריפט:
#!/bin/bash
# זהו הסקריפט הראשון שלנו
echo "Hello World!"
שורת ה-shebang מאפשרת לנו להגדיר איזה פרשן יופעל בעת הרצת הסקריפט. במקרה זה, אנו משתמשים ב-Bash.
עורכי טקסט נפוצים
במערכת Linux, לעתים אין אפשרות להשתמש בעורכי טקסט גרפיים ולכן חשוב להכיר עורך טקסט לפקודות:
vi
עורך vi הוא עורך טקסט ותיק שמותקן כברירת מחדל ברוב מערכות Linux. הוא מאפשר מעבר בין שלושה מצבים שונים: מצב ניווט, מצב עריכה ומצב פקודה.
nano
nano הוא עורך טקסט פשוט יותר מ-vi, ומאפשר עבודה ישירה על ידי הקלדה.
הדפסת פלט
כעת נמשיך בהדגמת פקודת echo שראינו כבר בשיעורים קודמים, המדפיסה ארגומנט לפלט הסטנדרטי:
$ echo "Hello World!"
Hello World!
נשתמש בהפניה לקובץ כדי לשלוח את הפלט לקובץ חדש שנקרא new_script:
$ echo 'echo "Hello World!"' > new_script
$ cat new_script
echo "Hello World!"
הפיכת סקריפט להרצה
כדי להריץ את הסקריפט, ניתן לנסות לקרוא לו ישירות:
$ new_script
/bin/bash: new_script: command not found
שגיאה זו נובעת מכך שהקובץ לא נמצא בספריות שמוגדרות במשתנה PATH של המערכת.
הוספת הרשאות הרצה
נבדוק את ההרשאות הנוכחיות של הקובץ ונוסיף לו הרשאות הרצה:
$ ls -l new_script
-rw-rw-r-- 1 user user 20 Apr 30 12:12 new_script
$ chmod +x new_script
$ ls -l new_script
-rwxrwxr-x 1 user user 20 Apr 30 12:12 new_script
כעת ניתן להריץ את הסקריפט בהצלחה:
$ ./new_script
Hello World!
הגדרת משתנים
משתנים הם חלק חשוב בכל שפת תכנות, וב-Bash אינם שונים. משתנה הוא מחרוזת או ערך שאנו יכולים להגדיר ולהשתמש בו בקוד שלנו. לדוגמה:
#!/bin/bash
# זהו המשתנה הראשון שלנו
username="קרול"
echo "שלום $username!"
שימו לב שעל מנת שהמערכת תזהה את המשתנה ותבצע עבורו תחליף, יש להשתמש בסימן דולר $ לפני שם המשתנה. התוצאה של הסקריפט תהיה:
$ ./new_script.sh
שלום קרול!
משתנים עם מרחבים
אם ברצוננו לשמור משתנה עם רווחים, יש להשתמש במרכאות כפולות:
#!/bin/bash
username="קרול סמית"
echo "שלום $username!"
הרצה של סקריפט זה תיתן את התוצאה הבאה:
$ ./new_script.sh
שלום קרול סמית!
שימוש בציטוטים עם משתנים
ב-Bash, מרכאות כפולות ומרכאות בודדות מתנהגות בצורה שונה. מרכאות כפולות מאפשרות תחליף משתנים, בעוד מרכאות בודדות מתייחסות לתוכן כפי שהוא, ללא תחליף:
#!/bin/bash
username="קרול סמית"
echo "שלום $username!"
echo 'שלום $username!'
התוצאה תהיה:
$ ./new_script.sh
שלום קרול סמית!
שלום $username!
כפי שניתן לראות, השימוש במרכאות בודדות מונע את תחליף המשתנים.
ארגומנטים
כמו שראינו בעבר עם הפקודות הבסיסיות בלינוקס, לדוגמה בפקודת rm testfile, הפקודה מכילה את הקובץ המבצע rm וארגומנט אחד testfile. ניתן להעביר ארגומנטים לסקריפט בזמן ההרצה, והם ישנו את אופן הפעולה שלו. הדבר מיושם בקלות.
#!/bin/bash
# זהו המשתנה הראשון שלנו
username=$1
echo "שלום $username!"
במקום להגדיר ערך למשתנה username ישירות בתוך הסקריפט, אנו משייכים לו את הערך של משתנה חדש $1. זה מתייחס לערך של הארגומנט הראשון.
$ ./new_script.sh קרול
שלום קרול!
כך ניתן לטפל בתשעה ארגומנטים לכל היותר, אך ניתן לטפל ביותר מזה בעזרת שיטות אחרות, אך זה מחוץ לתחום השיעור הזה. להלן דוגמה לשימוש בשני ארגומנטים:
#!/bin/bash
# זהו המשתנה הראשון שלנו
username1=$1
username2=$2
echo "שלום $username1 ו-$username2!"
$ ./new_script.sh קרול דייב
שלום קרול ודייב!
אם אחד הארגומנטים חסר, לדוגמה השני, הפקודה לא תציג שגיאה אלא תציג את המשתנה החסר כערך ריק:
$ ./new_script.sh קרול
שלום קרול ו-!
החזרת מספר הארגומנטים
משתנים כמו $1 ו-$2 מכילים את הערכים של הארגומנטים, ומשתנה $# מכיל את מספר הארגומנטים:
#!/bin/bash
username=$1
echo "שלום $username!"
echo "מספר הארגומנטים: $#."
$ ./new_script.sh קרול דייב
שלום קרול!
מספר הארגומנטים: 2.
לוגיקה מותנית
לוגיקה מותנית היא נושא נרחב, אבל נתמקד בסינטקס הנדרש ב-Bash, ששונה מעט משפות תכנות אחרות. לדוגמה, נבצע בדיקה האם ישנו משתמש אחד, ואם כן נציג ברכה. אחרת, נציג הודעת שגיאה.
#!/bin/bash
# סקריפט פשוט לברך משתמש אחד
if [ $# -eq 1 ]
then
username=$1
echo "שלום $username!"
else
echo "נא להזין ארגומנט אחד בלבד."
fi
echo "מספר הארגומנטים: $#."
התחביר של התנאי הוא בין if ו-fi. התנאי לבדיקה נמצא בין סוגריים מרובעים, והפעולה שאנו רוצים לבצע אם התנאי נכון תצוין אחרי then. התוצאה:
$ ./new_script.sh
נא להזין ארגומנט אחד בלבד.
מספר הארגומנטים: 0.
$ ./new_script.sh קרול
שלום קרול!
מספר הארגומנטים: 1.
השוואות נוספות
במקום השוואה ל--eq (שווה), ניתן להשתמש גם ב:
-ne: לא שווה-gt: גדול מ-ge: גדול או שווה-lt: קטן מ-le: קטן או שווה
תרגילים מודרכים
1. המשתמש הקליד את הפקודות הבאות:
$ PATH=~/scripts
$ ls
Command 'ls' is available in '/bin/ls'
The command could not be located because '/bin' is not included in the PATH
environment variable.
ls: command not found
◦ מה עשה המשתמש?
המשתמש החליף את הערך של משתנה הסביבה PATH עם התיקייה ~/scripts. הפקודה ls לא נמצאה מכיוון שתיקיית /bin אינה כלולה יותר ב-PATH. השינוי ישפיע רק על הסשן הנוכחי, ולאחר יציאה וכניסה מחדש הוא יתאפס.
◦ איזו פקודה תחבר בין הערך הנוכחי של PATH לתיקייה ~/scripts?
PATH=$PATH:~/scripts
2. בחן את הסקריפט הבא. שים לב שהוא משתמש ב-elif כדי לבדוק תנאי נוסף:
> /!bin/bash
> fruit1 = Apples
> fruit2 = Oranges
if [ $1 -lt $# ]
then
echo "This is like comparing $fruit1 and $fruit2!"
> elif [$1 -gt $2 ]
then
> echo '$fruit1 win!'
else
> echo "Fruit2 win!"
> done
השורות המסומנות ב-> מכילות שגיאות. תקן את השגיאות:
#!/bin/bash
fruit1=Apples
fruit2=Oranges
if [ $1 -lt $# ]
then
echo "This is like comparing $fruit1 and $fruit2!"
elif [ $1 -gt $2 ]
then
echo "$fruit1 win!"
else
echo "$fruit2 win!"
fi
3. מה תהיה התוצאה במצבים הבאים?
$ ./guided1.sh 3 0
Apples win!
$ ./guided1.sh 2 4
Oranges win!
$ ./guided1.sh 0 1
This is like comparing Apples and Oranges!
תרגילים חקרניים
1. כתוב סקריפט פשוט שיבדוק אם בדיוק שני ארגומנטים הוזנו. אם כן, הדפס את הארגומנטים בסדר הפוך:
#!/bin/bash
if [ $# -ne 2 ]
then
echo "Error"
else
echo "$2 $1"
fi
2. הקוד הזה נכון, אבל לא מדובר בהשוואה מספרית. חפש באינטרנט כיצד קוד זה שונה מהשימוש ב--eq.
תשובה: שימוש ב-== משווה מחרוזות. כלומר, אם התווים בשתי המחרוזות תואמים בדיוק, התנאי יהיה נכון.
3. יש משתנה סביבה שמדפיס את התיקייה הנוכחית. השתמש בפקודה env כדי לגלות את שם המשתנה הזה.
תשובה: PWD
4. בעזרת מה שלמדת בשאלות 2 ו-3, כתוב סקריפט קצר שמקבל ארגומנט. אם הוזן ארגומנט, בדוק אם הארגומנט תואם את שם התיקייה הנוכחית. אם כן, הדפס “כן”. אחרת, הדפס “לא”.
#!/bin/bash
if [ "$1" == "$PWD" ]
then
echo "כן"
else
echo "לא"
fi
קודי יציאה
שמתם לב שהסקריפט שלנו יכול להפיק שני מצבים: או שהוא מדפיס “Hello <user>!” או שהוא מדפיס הודעת שגיאה. זה די נורמלי עבור הרבה מהפקודות המרכזיות שלנו. בואו נבחן את הפקודה cat, שאתם כנראה כבר מכירים היטב.
בואו נשווה שימוש מוצלח ב-cat עם מצב שבו היא נכשלת. נזכיר שהדוגמה שלנו למעלה היא הסקריפט new_script.sh.
$ cat -n new_script.sh
1 #!/bin/bash
2
3 # A simple script to greet a single user.
4
5 if [ $# -eq 1 ]
6 then
7 username=$1
8
9 echo "Hello $username!"
10 else
11 echo "Please enter only one argument."
12 fi
13 echo "Number of arguments: $#."
הפקודה הזו מצליחה, ותבחינו שהדגל -n גם הדפיס מספרי שורות. זה יכול להיות מאוד מועיל כאשר מבצעים ניפוי באגים בסקריפטים, אך יש לציין שמספרי השורות אינם חלק מהסקריפט.
עכשיו נבדוק את הערך של משתנה מובנה חדש $?. לעת עתה, שימו לב לתוצאה:
$ echo $?
0
עכשיו נבחן מצב שבו cat נכשל. תחילה נראה הודעת שגיאה, ואז נבדוק את הערך של $?.
$ cat -n dummyfile.sh
cat: dummyfile.sh: No such file or directory
$ echo $?
1
ההסבר להתנהגות זו הוא כזה: כל ביצוע של הפקודה cat יחזיר קוד יציאה. קוד יציאה יאמר לנו אם הפקודה הצליחה או אם הייתה שגיאה. קוד יציאה של אפס מציין שהפקודה הושלמה בהצלחה. זה נכון כמעט לגבי כל פקודת לינוקס שבה תשתמשו. כל קוד יציאה אחר יצביע על שגיאה כלשהי. קוד היציאה של הפקודה האחרונה שבוצעה יישמר במשתנה $?.
קודי יציאה לרוב אינם נראים על ידי משתמשים, אבל הם מאוד שימושיים כשכותבים סקריפטים. לדוגמה, אם נכתוב סקריפט שמעתיק קבצים לכונן רשת מרוחק, ייתכנו מספר סיבות מדוע ההעתקה תיכשל: למשל, ייתכן שהמחשב המקומי אינו מחובר לרשת, או שהכונן המרוחק מלא. על ידי בדיקת קוד היציאה של פקודת ההעתקה, נוכל להתריע בפני המשתמש על בעיות במהלך הפעלת הסקריפט.
זו פרקטיקה טובה מאוד ליישם קודי יציאה, ולכן נעשה זאת עכשיו. יש לנו שני מסלולים בסקריפט שלנו, מסלול מוצלח ומסלול כושל. נשתמש באפס כדי לציין הצלחה, ובאחד כדי לציין כישלון.
1 #!/bin/bash
2
3 # A simple script to greet a single user.
4
5 if [ $# -eq 1 ]
6 then
7 username=$1
8
9 echo "Hello $username!"
10 exit 0
11 else
12 echo "Please enter only one argument."
13 exit 1
14 fi
15 echo "Number of arguments: $#."
$ ./new_script.sh Carol
Hello Carol!
$ echo $?
0
שימו לב שפקודת echo בשורה 15 התעלמה לחלוטין. השימוש ב-exit יפסיק את הסקריפט מיד, כך שהשורה הזו לא תיתקל.
טיפול בכמות גדולה של ארגומנטים
עד עכשיו הסקריפט שלנו יכול לטפל רק בשם משתמש אחד בכל פעם. כל כמות ארגומנטים אחרת תגרום לשגיאה. בואו נבדוק איך נוכל להפוך את הסקריפט שלנו לגמיש יותר.
התגובה הראשונה של משתמש עשויה להיות שימוש במשתני מיקום נוספים כמו $2, $3 וכן הלאה. למרבה הצער, איננו יכולים לצפות את כמות הארגומנטים שהמשתמש עשוי להכניס. כדי לפתור בעיה זו, כדאי להכיר כמה משתנים מובנים נוספים.
נשנה את הלוגיקה של הסקריפט שלנו. כאשר אין ארגומנטים כלל, תופיע שגיאה, אך כל כמות אחרת של ארגומנטים תעבוד בהצלחה. הסקריפט החדש יקרא friendly2.sh.
1 #!/bin/bash
2
3 # a friendly script to greet users
4
5 if [ $# -eq 0 ]
6 then
7 echo "Please enter at least one user to greet."
8 exit 1
9 else
10 echo "Hello $@!"
11 exit 0
12 fi
$ ./friendly2.sh Carol Dave Henry
Hello Carol Dave Henry!
ישנם שני משתנים מובנים שמכילים את כל הארגומנטים שעברו לסקריפט: $@ ו-$*. ברוב המקרים, שניהם מתנהגים באותו אופן. bash יפרש את הארגומנטים ויפריד ביניהם כאשר הוא מזהה רווחים. במשתנה $@ התוצאה נראית כך:
0 1 2 Carol Dave Henry
אם אתם מכירים שפות תכנות אחרות, ייתכן שתזהו משתנה כזה כמערך. מערכים ב-bash יכולים להיווצר על ידי רווח בין האלמנטים, כמו במשתנה FILES בסקריפט arraytest הבא:
FILES="/usr/sbin/accept /usr/sbin/pwck/ usr/sbin/chroot"
המכיל רשימה של מספר פריטים. עד כה זה לא היה מועיל במיוחד, כי לא הצגנו דרך לטפל בפריטים בנפרד.
לולאות For
בואו נחזור לדוגמה של arraytest שהוצגה קודם לכן. בדוגמה זו, אנו מגדירים מערך משלנו שנקרא FILES. מה שאנו צריכים כעת הוא דרך “לפרוק” את המשתנה הזה ולטפל בכל ערך בנפרד, אחד אחרי השני. כדי לעשות זאת, נשתמש במבנה שנקרא for loop, שנמצא בכל שפות התכנות.
זהו הסקריפט כולו:
#!/bin/bash
FILES="/usr/sbin/accept /usr/sbin/pwck/ usr/sbin/chroot"
for file in $FILES
do
ls -lh $file
done
$ ./arraytest
lrwxrwxrwx 1 root root 10 Apr 24 11:02 /usr/sbin/accept -> cupsaccept
-rwxr-xr-x 1 root root 54K Mar 22 14:32 /usr/sbin/pwck
-rwxr-xr-x 1 root root 43K Jan 14 07:17 /usr/sbin/chroot
אם תסתכלו שוב בדוגמה של friendly2.sh, תראו שאנחנו עובדים עם טווח ערכים שמכיל את כל הארגומנטים המשתנים במשתנה $@. למען הבהירות, נקרא למשתנה הזה username. הסקריפט שלנו נראה כך:
1 #!/bin/bash
2
3 # a friendly script to greet users
4
5 if [ $# -eq 0 ]
6 then
7 echo "Please enter at least one user to greet."
8 exit 1
9 else
10 for username in $@
11 do
12 echo "Hello $username!"
13 done
14 exit 0
15 fi
שימו לב שהמשתנה שהגדרתם כאן יכול להיקרא בכל שם שתבחרו, וכל השורות שבין do…done יבוצעו פעם אחת עבור כל אלמנט במערך. בואו נבחן את הפלט מהסקריפט:
$ ./friendly2.sh Carol Dave Henry
Hello Carol!
Hello Dave!
Hello Henry!
עכשיו נניח שאנחנו רוצים להפוך את הפלט שלנו ליותר ידידותי. אנחנו רוצים שהברכה תהיה בשורה אחת.
1 #!/bin/bash
2
3 # a friendly script to greet users
4
5 if [ $# -eq 0 ]
6 then
7 echo "Please enter at least one user to greet."
8 exit 1
9 else
10 echo -n "Hello $1"
11 shift
12 for username in $@
13 do
14 echo -n ", and $username"
15 done
16 echo "!"
17 exit 0
18 fi
כמה הערות:
- שימוש ב-
-nעםechoיבטל את ההדפסה האוטומטית של שורה חדשה לאחר ההדפסה. זה אומר שכל פקודות ה-echoיודפסו באותה שורה, והשורה החדשה תודפס רק לאחר סימן הקריאה!בשורה 16.
טיפול במספר רב של ארגומנטים
עד כה הסקריפט שלנו יכול לטפל רק בשם משתמש אחד בכל פעם. כל מספר ארגומנטים אחר יגרום לשגיאה. בואו נבדוק איך אפשר להפוך את הסקריפט שלנו לגמיש יותר.
האינסטינקט הראשון של המשתמש עשוי להיות להשתמש במשתני מיקום נוספים כמו $2, $3 וכן הלאה. למרבה הצער, איננו יכולים לצפות כמה ארגומנטים המשתמש עשוי להזין. כדי לפתור בעיה זו, נעשה שימוש במשתנים מובנים נוספים.
נשנה את הלוגיקה של הסקריפט שלנו. אם לא הוזן אף ארגומנט, תוצג שגיאה, אך כל מספר אחר של ארגומנטים יגרום להצלחה. הסקריפט החדש ייקרא friendly2.sh.
1 #!/bin/bash
2
3 # a friendly script to greet users
4
5 if [ $# -eq 0 ]
6 then
7 echo "Please enter at least one user to greet."
8 exit 1
9 else
10 echo "Hello $@!"
11 exit 0
12 fi
$ ./friendly2.sh Carol Dave Henry
Hello Carol Dave Henry!
ישנם שני משתנים מובנים המכילים את כל הארגומנטים שהוזנו לסקריפט: $@ ו-$*. שניהם מתנהגים לרוב באותו אופן. bash מפרש את הארגומנטים ומפריד ביניהם כשהוא מזהה רווחים. התוכן של $@ נראה כך:
0 1 2 Carol Dave Henry
אם אתם מכירים שפות תכנות אחרות, ייתכן שתזהו את המשתנה הזה כמערך. מערכים ב-bash ניתנים ליצירה על ידי רווח בין האלמנטים, כמו המשתנה FILES בסקריפט arraytest הבא:
FILES="/usr/sbin/accept /usr/sbin/pwck/ usr/sbin/chroot"
כעת נראה איך נוכל לטפל בכל פריט בנפרד.
לולאות For
כדי לטפל בכל פריט בנפרד במערך, נשתמש בלולאת for. מבנה זה קיים בכל שפות התכנות.
הסקריפט המלא:
#!/bin/bash
FILES="/usr/sbin/accept /usr/sbin/pwck/ usr/sbin/chroot"
for file in $FILES
do
ls -lh $file
done
$ ./arraytest
lrwxrwxrwx 1 root root 10 Apr 24 11:02 /usr/sbin/accept -> cupsaccept
-rwxr-xr-x 1 root root 54K Mar 22 14:32 /usr/sbin/pwck
-rwxr-xr-x 1 root root 43K Jan 14 07:17 /usr/sbin/chroot
בסקריפט friendly2.sh, אנחנו עובדים עם משתנה שמכיל את כל הארגומנטים באמצעות $@. בואו נראה את השינוי:
1 #!/bin/bash
2
3 # a friendly script to greet users
4
5 if [ $# -eq 0 ]
6 then
7 echo "Please enter at least one user to greet."
8 exit 1
9 else
10 for username in $@
11 do
12 echo "Hello $username!"
13 done
14 exit 0
15 fi
כך תיראה ההרצה של הסקריפט:
$ ./friendly2.sh Carol Dave Henry
Hello Carol!
Hello Dave!
Hello Henry!
נניח שנרצה להפוך את ההודעה לידידותית יותר, כך שכל הברכה תופיע בשורה אחת:
1 #!/bin/bash
2
3 # a friendly script to greet users
4
5 if [ $# -eq 0 ]
6 then
7 echo "Please enter at least one user to greet."
8 exit 1
9 else
10 echo -n "Hello $1"
11 shift
12 for username in $@
13 do
14 echo -n ", and $username"
15 done
16 echo "!"
17 exit 0
18 fi
הערות:
- שימוש ב-
-nעםechoימנע הדפסה אוטומטית של שורה חדשה אחרי ההדפסה, כך שכל ההדפסות יופיעו בשורה אחת.
שימוש בביטויים רגולריים לבדיקת שגיאות
ייתכן שנרצה לבדוק שהארגומנטים שהמשתמש מזין תקינים. למשל, נוודא שכל השמות שהוזנו מכילים רק אותיות. לשם כך נשתמש ב-grep עם ביטויים רגולריים.
$ echo Animal | grep "^[A-Za-z]*$"
Animal
$ echo $?
0
$ echo 4n1ml | grep "^[A-Za-z]*$"
$ echo $?
1
הסקריפט המשודרג:
1 #!/bin/bash
2
3 # a friendly script to greet users
4
5 if [ $# -eq 0 ]
6 then
7 echo "Please enter at least one user to greet."
8 exit 1
9 else
10 for username in $@
11 do
12 echo $username | grep "^[A-Za-z]*$" > /dev/null
13 if [ $? -eq 1 ]
14 then
15 echo "ERROR: Names must only contain letters."
16 exit 2
17 else
18 echo "Hello $username!"
19 fi
20 done
21 exit 0
22 fi
בשורה 12, אנחנו מפנים את הפלט ל-/dev/null כדי להסתיר אותו ורק לבדוק את קוד היציאה. קוד יציאה 2 ייצג שגיאה בקלט. כך יראה הפלט:
$ ./friendly2.sh Carol Dave Henry
Hello Carol!
Hello Dave!
Hello Henry!
$ ./friendly2.sh 42 Carol Dave Henry
ERROR: Names must only contain letters.
$ echo $?
2
תרגילים מודרכים
1. קרא את תוכן הקובץ script1.sh הבא:
#!/bin/bash
if [ $# -lt 1 ]
then
echo "This script requires at least 1 argument."
exit 1
fi
echo $1 | grep "^[A-Z]*$" > /dev/null
if [ $? -ne 0 ]
then
echo "no cake for you!"
exit 2
fi
echo "here's your cake!"
exit 0
מה הפלט של הפקודות הבאות?
◦ Command: ./script1.sh
Output: This script requires at least 1 argument.
◦ Command: echo $?
Output: 1
◦ Command: ./script1.sh cake
Output: no cake for you!
◦ Command: echo $?
Output: 2
◦ Command: ./script1.sh CAKE
Output: here’s your cake!
◦ Command: echo $?
Output: 0
2. קרא את תוכן הקובץ script2.sh:
for filename in $1/*.txt
do
cp $filename $filename.bak
done
תאר את מטרת הסקריפט כפי שהבנת אותה.
תשובה: הסקריפט יוצר גיבויים לכל הקבצים שמסתיימים ב-.txt בתת-התיקייה שמוגדרת בארגומנט הראשון.
תרגילים חקרניים
1. צור סקריפט שייקח מספר ארגומנטים מהמשתמש וידפיס רק את הארגומנטים שהם מספרים הגדולים מ-10.
#!/bin/bash
for i in $@
do
echo $i | grep "^[0-9]*$" > /dev/null
if [ $? -eq 0 ]
then
if [ $i -gt 10 ]
then
echo -n "$i "
fi
fi
done
echo ""
פתרונות לתרגילים מודרכים
1. קרא את תוכן הקובץ script1.sh הבא:
#!/bin/bash
if [ $# -lt 1 ]
then
echo "This script requires at least 1 argument."
exit 1
fi
echo $1 | grep "^[A-Z]*$" > /dev/null
if [ $? -ne 0 ]
then
echo "no cake for you!"
exit 2
fi
echo "here's your cake!"
exit 0
מה הפלט של הפקודות הבאות?
◦ Command: ./script1.sh
Output: This script requires at least 1 argument.
◦ Command: echo $?
Output: 1
◦ Command: ./script1.sh cake
Output: no cake for you!
◦ Command: echo $?
Output: 2
◦ Command: ./script1.sh CAKE
Output: here’s your cake!
◦ Command: echo $?
Output: 0
2. קרא את תוכן הקובץ script2.sh:
for filename in $1/*.txt
do
cp $filename $filename.bak
done
תשובה: הסקריפט יוצר גיבויים לכל הקבצים שמסתיימים ב-.txt בתת-התיקייה שמוגדרת בארגומנט הראשון.
פתרונות לתרגילים חקרניים
1. צור סקריפט שייקח מספר ארגומנטים מהמשתמש וידפיס רק את הארגומנטים שהם מספרים הגדולים מ-10.
#!/bin/bash
for i in $@
do
echo $i | grep "^[0-9]*$" > /dev/null
if [ $? -eq 0 ]
then
if [ $i -gt 10 ]
then
echo -n "$i "
fi
fi
done
echo ""
Printing Output
Let’s begin by demonstrating a command that you may have seen in previous lessons: echo will print
an argument to standard output.
$ echo “Hello World!”
Hello World!
Now, we will use file redirection to send this command to a new file called new_script.
Linux Essentials (Version 1.6) | Topic 3: The Power of the Command Line
212 | learning.lpi.org | Licensed for Cyber School. | Version: 2022-04-29
$ echo ‘echo “Hello World!”‘ > new_script
$ cat new_script
echo “Hello World!”
The file new_script now contains the same command as before.
Making a Script Executable
Let’s demonstrate some of the steps required to make this file execute the way we expect it to. A
user’s first thought might be to simply type the name of the script, the way they might type in the
name of any other command:
$ new_script
/bin/bash: new_script: command not found
We can safely assume that new_script exists in our current location, but notice that the error
message isn’t telling us that the file doesn’t exist, it is telling us that the command doesn’t exist. It
would be useful to discuss how Linux handles commands and executables.
Commands and PATH
When we type the ls command into the shell, for example, we are executing a file called ls that
exists in our filesystem. You can prove this by using which:
$ which ls
/bin/ls
It would quickly become tiresome to type in the absolute path of ls every time we wish to look at
the contents of a directory, so Bash has an environment variable which contains all the directories
where we might find the commands we wish to run. You can view the contents of this variable by
using echo.
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/
games:/snap/bin
Each of these locations is where the shell expects to find a command, delimited with colons (:). You
will notice that /bin is present, but it is safe to assume that our current location is not. The shell will
Linux Essentials (Version 1.6) | 3.3 Turning Commands into a Script
Version: 2022-04-29 | Licensed for Cyber School. | learning.lpi.org | 213
search for new_script in each of these directories, but it will not find it and therefore will throw the
error we saw above.
There are three solutions to this issue: we can move new_script into one of the PATH directories, we
can add our current directory to PATH, or we can change the way we attempt to call the script. The
latter solution is easiest, it simply requires us to specify the current location when calling the script
using dot slash (./).
$ ./new_script
/bin/bash: ./new_script: Permission denied
The error message has changed, which indicates that we have made some progress.
Execute Permissions
The first investigation a user should do in this case is to use ls -l to look at the file:
$ ls -l new_script
-rw-rw-r– 1 user user 20 Apr 30 12:12 new_script
We can see that the permissions for this file are set to 664 by default. We have not set this file to
have execute permissions yet.
$ chmod +x new_script
$ ls -l new_script
-rwxrwxr-x 1 user user 20 Apr 30 12:12 new_script
This command has given execute permissions to all users. Be aware that this might be a security
risk, but for now this is an acceptable level of permission.
$ ./new_script
Hello World!
We are now able to execute our script.
Defining the Interpreter
As we have demonstrated, we were able to simply enter text into a file, set it as an executable, and
Linux Essentials (Version 1.6) | Topic 3: The Power of the Command Line
214 | learning.lpi.org | Licensed for Cyber School. | Version: 2022-04-29
run it. new_script is functionally still a normal text file, but we managed to have it be interpreted by
Bash. But what if it is written in Perl, or Python?
It is very good practice to specify the type of interpreter we want to use in the first line of a script.
This line is called a bang line or more commonly a shebang. It indicates to the system how we want
this file to be executed. Since we are learning Bash, we will be using the absolute path to our Bash
executable, once again using which:
$ which bash
/bin/bash
Our shebang starts with a hash sign and exclamation mark, followed by the absolute path above.
Let’s open new_script in a text editor and insert the shebang. Let’s also take the opportunity to
insert a comment into our script. Comments are ignored by the interpreter. They are written for the
benefit of other users wishing to understand your script.
#!/bin/bash
# This is our first comment. It is also good practice to document all scripts.
echo “Hello World!”
We will make one additional change to the filename as well: we will save this file as new_script.sh.
The file suffix “.sh” does not change the execution of the file in any way. It is a convention that bash
scripts be labelled with .sh or .bash in order to identify them more easily, the same way that Python
scripts are usually identified with the suffix .py.
Common Text Editors
Linux users often have to work in an environment where graphical text editors are not available. It is
therefore highly recommended to develop at least some familiarity with editing text files from the
command line. Two of the most common text editors are vi and nano.
vi
vi is a venerable text editor and is installed by default on almost every Linux system in existence. vi
spawned a clone called vi IMproved or vim which adds some functionality but maintains the interface
of vi. While working with vi is daunting for a new user, the editor is popular and well-loved by
users who learn its many features.
Linux Essentials (Version 1.6) | 3.3 Turning Commands into a Script
Version: 2022-04-29 | Licensed for Cyber School. | learning.lpi.org | 215
The most important difference between vi and applications such as Notepad is that vi has three
different modes. On startup, the keys H , J , K and L are used to navigate, not to type. In this navigation
mode, you can press I to enter insert mode. At this point, you may type normally. To exit insert mode,
you press Esc to return to navigation mode. From navigation mode, you can press : to enter command
mode. From this mode, you can save, delete, quit or change options.
While vi has a learning curve, the different modes can in time allow a savvy user to become more
efficient than with other editors.
nano
nano is a newer tool, built to be simple and easier to use than vi. nano does not have different modes.
Instead, a user on startup can begin typing, and uses Ctrl to access the tools printed at the bottom of
the screen.
.
[ Welcome to nano. For basic help, type Ctrl+G. ]
^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos
M-U Undo
^X Exit ^R Read File ^\ Replace ^U Uncut Text ^T To Spell ^_ Go To Line
M-E Redo
Text editors are a matter of personal preference, and the editor that you choose to use will have no
bearing on this lesson. But becoming familiar and comfortable with one or more text editors will pay
off in the future.
Variables
Variables are an important part of any programming language, and Bash is no different. When you
start a new session from the terminal, the shell already sets some variables for you. The PATH
variable is an example of this. We call these environment variables, because they usually define
characteristics of our shell environment. You can modify and add environment variables, but for
now let’s focus on setting variables inside our script.
We will modify our script to look like this:
Linux Essentials (Version 1.6) | Topic 3: The Power of the Command Line
216 | learning.lpi.org | Licensed for Cyber School. | Version: 2022-04-29
#!/bin/bash
# This is our first comment. It is also good practice to comment all scripts.
username=Carol
echo “Hello $username!”
In this case, we have created a variable called username and we have assigned it the value of Carol.
Please note that there are no spaces between the variable name, the equals sign, or the assigned
value.
In the next line, we have used the echo command with the variable, but there is a dollar sign ($) in
front of the variable name. This is important, since it indicates to the shell that we wish to treat
username as a variable, and not just a normal word. By entering $username in our command, we
indicate that we want to perform a substitution, replacing the name of a variable with the value
assigned to that variable.
Executing the new script, we get this output:
$ ./new_script.sh
Hello Carol!
• Variable names must contain only alphanumeric characters or underscores, and are case
sensitive. Username and username will be treated as separate variables.
• Variable substitution may also have the format ${username}, with the addition of the { }. This is
also acceptable.
• Variables in Bash have an implicit type, and are considered strings. This means that performing
math functions in Bash is more complicated than it would be in other programming languages
such as C/C++:
Linux Essentials (Version 1.6) | 3.3 Turning Commands into a Script
Version: 2022-04-29 | Licensed for Cyber School. | learning.lpi.org | 217
#!/bin/bash
# This is our first comment. It is also good practice to comment all scripts.
username=Carol
x=2
y=4
z=$x+$y
echo “Hello $username!”
echo “$x + $y”
echo “$z”
$ ./new_script.sh
Hello Carol!
2 + 4
2+4
Using Quotes with Variables
Let’s make the following change to the value of our variable username:
#!/bin/bash
# This is our first comment. It is also good practice to comment all scripts.
username=Carol Smith
echo “Hello $username!”
Running this script will give us an error:
$ ./new_script.sh
./new_script.sh: line 5: Smith: command not found
Hello !
Keep in mind that Bash is an interpreter, and as such it interprets our script line-by-line. In this case,
it correctly interprets username=Carol to be setting a variable username with the value Carol. But it
then interprets the space as indicating the end of that assignment, and Smith as being the name of a
command. In order to have the space and the name Smith be included as the new value of our
Linux Essentials (Version 1.6) | Topic 3: The Power of the Command Line
218 | learning.lpi.org | Licensed for Cyber School. | Version: 2022-04-29
variable, we will put double quotes (“) around the name.
#!/bin/bash
# This is our first comment. It is also good practice to comment all scripts.
username=”Carol Smith”
echo “Hello $username!”
$ ./new_script.sh
Hello Carol Smith!
One important thing to note in Bash is that double quotes and single quotes (‘) behave very
differently. Double quotes are considered “weak”, because they allow the interpreter to perform
substitution inside the quotes. Single quotes are considered “strong”, because they prevent any
substitution from occurring. Consider the following example:
#!/bin/bash
# This is our first comment. It is also good practice to comment all scripts.
username=”Carol Smith”
echo “Hello $username!”
echo ‘Hello $username!’
$ ./new_script.sh
Hello Carol Smith!
Hello $username!
In the second echo command, the interpreter has been prevented from substituting $username with
Carol Smith, and so the output is taken literally
Arguments
You are already familiar with using arguments in the Linux core utilities. For example, rm testfile
contains both the executable rm and one argument testfile. Arguments can be passed to the script
upon execution, and will modify how the script behaves. They are easily implemented.
Linux Essentials (Version 1.6) | 3.3 Turning Commands into a Script
Version: 2022-04-29 | Licensed for Cyber School. | learning.lpi.org | 219
#!/bin/bash
# This is our first comment. It is also good practice to comment all scripts.
username=$1
echo “Hello $username!”
Instead of assigning a value to username directly inside the script, we are assigning it the value of a
new variable $1. This refers to the value of the first argument.
$ ./new_script.sh Carol
Hello Carol!
The first nine arguments are handled in this way. There are ways to handle more than nine
arguments, but that is outside the scope of this lesson. We will demonstrate an example using just
two arguments:
#!/bin/bash
# This is our first comment. It is also good practice to comment all scripts.
username1=$1
username2=$2
echo “Hello $username1 and $username2!”
$ ./new_script.sh Carol Dave
Hello Carol and Dave!
There is an important consideration when using arguments: In the example above, there are two
arguments Carol and Dave, assigned to $1 and $2 respectively. If the second argument is missing, for
example, the shell will not throw an error. The value of $2 will simply be null, or nothing at all.
$ ./new_script.sh Carol
Hello Carol and !
In our case, it would be a good idea to introduce some logic to our script so that different conditions
will affect the output that we wish to print. We will start by introducing another helpful variable and
Linux Essentials (Version 1.6) | Topic 3: The Power of the Command Line
220 | learning.lpi.org | Licensed for Cyber School. | Version: 2022-04-29
then move on to creating if statements.
Returning the Number of Arguments
While variables such as $1 and $2 contain the value of positional arguments, another variable $#
contains the number of arguments.
#!/bin/bash
# This is our first comment. It is also good practice to comment all scripts.
username=$1
echo “Hello $username!”
echo “Number of arguments: $#.”
$ ./new_script.sh Carol Dave
Hello Carol!
Number of arguments: 2.
Conditional Logic
The use of conditional logic in programming is a vast topic, and won’t be covered deeply in this
lesson. We will focus on the syntax of conditionals in Bash, which differs from most other
programming languages.
Let’s begin by reviewing what we hope to achieve. We have a simple script which should be able to
print a greeting to a single user. If there is anything other than one user, we should print an error
message.
• The condition we are testing is the number of users, which is contained in the variable $#. We
would like to know if the value of $# is 1.
• If the condition is true, the action we will take is to greet the user.
• If the condition is false, we will print an error message.
Now that the logic is clear, we will focus on the syntax required to implement this logic.
Linux Essentials (Version 1.6) | 3.3 Turning Commands into a Script
Version: 2022-04-29 | Licensed for Cyber School. | learning.lpi.org | 221
#!/bin/bash
# A simple script to greet a single user.
if [ $# -eq 1 ]
then
username=$1
echo “Hello $username!”
else
echo “Please enter only one argument.”
fi
echo “Number of arguments: $#.”
The conditional logic is contained between if and fi. The condition to test is located between
square brackets [ ], and the action to take should the condition be true is indicated after then. Note
the spaces between the square brackets and the logic contained. Omitting this space will cause
errors.
This script will output either our greeting, or the error message. But it will always print the Number
of arguments line.
$ ./new_script.sh
Please enter only one argument.
Number of arguments: 0.
$ ./new_script.sh Carol
Hello Carol!
Number of arguments: 1.
Take note of the if statement. We have used -eq to do a numerical comparison. In this case, we are
testing that the value of $# is equal to one. The other comparisons we can perform are:
-ne
Not equal to
-gt
Greater than
-ge
Greater than or equal to
Linux Essentials (Version 1.6) | Topic 3: The Power of the Command Line
222 | learning.lpi.org | Licensed for Cyber School. | Version: 2022-04-29
-lt
Less than
-le
Less than or equal to
Guided Exercises
1. The user types the following to their shell:
$ PATH=~/scripts
$ ls
Command ‘ls’ is available in ‘/bin/ls’
The command could not be located because ‘/bin’ is not included in the PATH
environment variable.
ls: command not found
◦ What has the user done?
◦ What command will combine the current value of PATH with the new directory ~/scripts?
2. Consider the following script. Notice that it is using elif to check for a second condition:
> /!bin/bash
> fruit1 = Apples
> fruit2 = Oranges
if [ $1 -lt $# ]
then
echo “This is like comparing $fruit1 and $fruit2!”
> elif [$1 -gt $2 ]
then
> echo ‘$fruit1 win!’
else
> echo “Fruit2 win!”
> done
◦ The lines marked with a > contain errors. Fix the errors.
3. What will the output be in the following situations?
$ ./guided1.sh 3 0
Linux Essentials (Version 1.6) | Topic 3: The Power of the Command Line
224 | learning.lpi.org | Licensed for Cyber School. | Version: 2022-04-29
$ ./guided1.sh 2 4
$ ./guided1.sh 0 1
Linux Essentials (Version 1.6) | 3.3 Turning Commands into a Script
Version: 2022-04-29 | Licensed for Cyber School. | learning.lpi.org | 225
Explorational Exercises
1. Write a simple script that will check if exactly two arguments are passed. If so, print the
arguments in reverse order. Consider this example (note: your code may look different than this,
but should lead to the same output):
if [ $1 == $number ]
then
echo “True!”
fi
2. This code is correct, but it is not a number comparison. Use an internet search to discover how
this code is different from using -eq.
3. There is an environment variable that will print the current directory. Use env to discover the
name of this variable.
4. Using what you have learned in questions 2 and 3, write a short script that accepts an argument.
If an argument is passed, check if that argument matches the name of the current directory. If so,
print yes. Otherwise, print no.
Linux Essentials (Version 1.6) | Topic 3: The Power of the Command Line
226 | learning.lpi.org | Licensed for Cyber School. | Version: 2022-04-29
Summary
In this section, you learned:
• How to create and execute simple scripts
• How to use a shebang to specify an interpreter
• How to set and use variables inside scripts
• How to handle arguments in scripts
• How to construct if statements
• How to compare numbers using numerical operators
Commands used in the exercises:
echo
Print a string to standard output.
env
Prints all environment variables to standard output.
which
Prints the absolute path of a command.
chmod
Changes permissions of a file.
Special variables used in the exercises:
$1, $2, … $9
Contain positional arguments passed to the script.
$#
Contains the number of arguments passed to the script.
$PATH
Contains the directories that have executables used by the system.
Operators used in the exercises:
Linux Essentials (Version 1.6) | 3.3 Turning Commands into a Script
Version: 2022-04-29 | Licensed for Cyber School. | learning.lpi.org | 227
-ne
Not equal to
-gt
Greater than
-ge
Greater than or equal to
-lt
Less than
-le
Less than or equal to
Linux Essentials (Version 1.6) | Topic 3: The Power of the Command Line
228 | learning.lpi.org | Licensed for Cyber School. | Version: 2022-04-29
Answers to Guided Exercises
1. The user types the following into their shell:
$ PATH=~/scripts
$ ls
Command ‘ls’ is available in ‘/bin/ls’
The command could not be located because ‘/bin’ is not included in the PATH
environment variable.
ls: command not found
◦ What has the user done?
The user has overwritten the contents of PATH with the directory ~/scripts. The ls
command can no longer be found, since it isn’t contained in PATH. Note that this change
only affects the current session, logging out and back in with revert the change.
◦ What command will combine the current value of PATH with the new directory ~/scripts?
PATH=$PATH:~/scripts
2. Consider the following script. Notice that it is using elif to check for a second condition:
> /!bin/bash
> fruit1 = Apples
> fruit2 = Oranges
if [ $1 -lt $# ]
then
echo “This is like comparing $fruit1 and $fruit2!”
> elif [$1 -gt $2 ]
then
> echo ‘$fruit1 win!’
else
> echo “Fruit2 win!”
> done
◦ The lines marked with a > contain errors. Fix the errors.
Linux Essentials (Version 1.6) | 3.3 Turning Commands into a Script
Version: 2022-04-29 | Licensed for Cyber School. | learning.lpi.org | 229
#!/bin/bash
fruit1=Apples
fruit2=Oranges
if [ $1 -lt $# ]
then
echo “This is like comparing $fruit1 and $fruit2!”
elif [ $1 -gt $2 ]
then
echo “$fruit1 win!”
else
echo “$fruit2 win!”
fi
3. What will the output be in the following situations?
$ ./guided1.sh 3 0
Apples win!
$ ./guided1.sh 2 4
Oranges win!
$ ./guided1.sh 0 1
This is like comparing Apples and Oranges!
Linux Essentials (Version 1.6) | Topic 3: The Power of the Command Line
230 | learning.lpi.org | Licensed for Cyber School. | Version: 2022-04-29
Answers to Explorational Exercises
1. Write a simple script that will check if exactly two arguments are passed. If so, print the
arguments in reverse order. Consider this example (note: your code may look different than this,
but should lead to the same output):
if [ $1 == $number ]
then
echo “True!”
fi
#!/bin/bash
if [ $# -ne 2 ]
then
echo “Error”
else
echo “$2 $1”
fi
2. This code is correct, but it is not a number comparison. Use an internet search to discover how
this code is different from using -eq.
Using == will compare strings. That is, if the characters of both variables match up exactly, then
the condition is true.
abc == abc true
abc == ABC false
1 == 1 true
1+1 == 2 false
String comparisons lead to unexpected behavior if you are testing for numbers.
3. There is an environment variable that will print the current directory. Use env to discover the
name of this variable.
PWD
4. Using what you have learned in questions 2 and 3, write a short script that accepts an argument.
Linux Essentials (Version 1.6) | 3.3 Turning Commands into a Script
Version: 2022-04-29 | Licensed for Cyber School. | learning.lpi.org | 231
If an argument is passed, check if that argument matches the name of the current directory. If so,
print yes. Otherwise, print no.
#!/bin/bash
if [ “$1” == “$PWD” ]
then
echo “yes”
else
echo “no”
fi
Exit Codes You will notice that our script has two possible states: either it prints “Hello !” or it prints an error message. This is quite normal for many of our core utilities. Consider cat, which you are no doubt becoming very familiar with. Let’s compare a successful use of cat with a situation where it fails. A reminder that our example above is a script called new_script.sh. $ cat -n new_script.sh 1 #!/bin/bash 2 3 # A simple script to greet a single user. 4 5 if [ $# -eq 1 ] 6 then 7 username=$1 8 9 echo “Hello $username!” 10 else 11 echo “Please enter only one argument.” 12 fi 13 echo “Number of arguments: $#.” This command succeeds, and you will notice that the -n flag has also printed line numbers. These are very helpful when debugging scripts, but please note that they are not part of the script. Now we are going to check the value of a new built-in variable $?. For now, just notice the output: Linux Essentials (Version 1.6) | Topic 3: The Power of the Command Line 234 | learning.lpi.org | Licensed for Cyber School. | Version: 2022-04-29 $ echo $? 0 Now let’s consider a situation where cat will fail. First we will see an error message, and then check the value of $?. $ cat -n dummyfile.sh cat: dummyfile.sh: No such file or directory $ echo $? 1 The explanation for this behaviour is this: any execution of the cat utility will return an exit code. An exit code will tell us if the command succeeded, or experienced an error. An exit code of zero indicates that the command completed successfully. This is true for almost every Linux command that you work with. Any other exit code will indicate an error of some kind. The exit code of the last command to run will be stored in the variable $?. Exit codes are usually not seen by human users, but they are very useful when writing scripts. Consider a script where we may be copying files to a remote network drive. There are many ways that the copy task may have failed: for example our local machine might not be connected to the network, or the remote drive might be full. By checking the exit code of our copy utility, we can alert the user to problems when running the script. It is very good practice to implement exit codes, so we will do this now. We have two paths in our script, a success and a failure. Let’s use zero to indicate success, and one to indicate failure. Linux Essentials (Version 1.6) | 3.3 Turning Commands into a Script Version: 2022-04-29 | Licensed for Cyber School. | learning.lpi.org | 235 1 #!/bin/bash 2 3 # A simple script to greet a single user. 4 5 if [ $# -eq 1 ] 6 then 7 username=$1 8 9 echo “Hello $username!” 10 exit 0 11 else 12 echo “Please enter only one argument.” 13 exit 1 14 fi 15 echo “Number of arguments: $#.” $ ./new_script.sh Carol Hello Carol! $ echo $? 0 Notice that the echo command on line 15 was ignored entirely. Using exit will end the script immediately, so this line is never encountered.
Handling Many Arguments
So far our script can only handle a single username at a time. Any number of arguments besides one
will cause an error. Let’s explore how we can make this script more versatile.
A user’s first instinct might be to use more positional variables such as $2, $3 and so on.
Unfortunately, we can’t anticipate the number of arguments that a user might choose to use. To
solve this issue, it will be helpful to introduce more built-in variables.
We will modify the logic of our script. Having zero arguments should cause an error, but any other
number of arguments should be successful. This new script will be called friendly2.sh.
Linux Essentials (Version 1.6) | Topic 3: The Power of the Command Line
236 | learning.lpi.org | Licensed for Cyber School. | Version: 2022-04-29
1 #!/bin/bash
2
3 # a friendly script to greet users
4
5 if [ $# -eq 0 ]
6 then
7 echo “Please enter at least one user to greet.”
8 exit 1
9 else
10 echo “Hello $@!”
11 exit 0
12 fi
$ ./friendly2.sh Carol Dave Henry
Hello Carol Dave Henry!
There are two built-in variables which contain all arguments passed to the script: $@ and $*. For the
most part, both behave the same. Bash will parse the arguments, and separate each argument when it
encounters a space between them. In effect, the contents of $@ look like this:
0 1 2
Carol Dave Henry
If you are familiar with other programming languages, you might recognize this type of variable as
an array. Arrays in Bash can be created simply by putting space between elements like the variable
FILES in script arraytest below:
FILES=”/usr/sbin/accept /usr/sbin/pwck/ usr/sbin/chroot”
It contains a list of many items. So far this isn’t very helpful, because we have not yet introduced any
way of handling these items individually.
For Loops
Let’s refer to the arraytest example shown before. If you recall, in this example we are specifying
an array of our own called FILES. What we need is a way to “unpack” this variable and access each
individual value, one after the other. To do this, we will use a structure called a for loop, which is
present in all programming languages. There are two variables that we will refer to: one is the range,
and the other is for the individual value that we are currently working on. This is the script in its
Linux Essentials (Version 1.6) | 3.3 Turning Commands into a Script
Version: 2022-04-29 | Licensed for Cyber School. | learning.lpi.org | 237
entirety:
#!/bin/bash
FILES=”/usr/sbin/accept /usr/sbin/pwck/ usr/sbin/chroot”
for file in $FILES
do
ls -lh $file
done
$ ./arraytest
lrwxrwxrwx 1 root root 10 Apr 24 11:02 /usr/sbin/accept -> cupsaccept
-rwxr-xr-x 1 root root 54K Mar 22 14:32 /usr/sbin/pwck
-rwxr-xr-x 1 root root 43K Jan 14 07:17 /usr/sbin/chroot
If you refer again to the friendly2.sh example above, you can see that we are working with a range
of values contained within a single variable $@. For clarity’s sake, we will call the latter variable
username. Our script now looks like this:
1 #!/bin/bash
2
3 # a friendly script to greet users
4
5 if [ $# -eq 0 ]
6 then
7 echo “Please enter at least one user to greet.”
8 exit 1
9 else
10 for username in $@
11 do
12 echo “Hello $username!”
13 done
14 exit 0
15 fi
Remember that the variable that you define here can be named whatever you wish, and that all the
lines inside do… done will be executing once for each element of the array. Let’s observe the output
from our script:
Linux Essentials (Version 1.6) | Topic 3: The Power of the Command Line
238 | learning.lpi.org | Licensed for Cyber School. | Version: 2022-04-29
$ ./friendly2.sh Carol Dave Henry
Hello Carol!
Hello Dave!
Hello Henry!
Now let’s assume that we want to make our output seem a little more human. We want our greeting
to be on one line.
1 #!/bin/bash
2
3 # a friendly script to greet users
4
5 if [ $# -eq 0 ]
6 then
7 echo “Please enter at least one user to greet.”
8 exit 1
9 else
10 echo -n “Hello $1”
11 shift
12 for username in $@
13 do
14 echo -n “, and $username”
15 done
16 echo “!”
17 exit 0
18 fi
A couple of notes:
• Using -n with echo will suppress the newline after printing. This means that all echoes will print
to the same line, and the newline will be printed only after the !` on line 16.
• The shift command will remove the first element of our array, so that this:
0 1 2
Carol Dave Henry
Becomes this:
0 1
Dave Henry
Linux Essentials (Version 1.6) | 3.3 Turning Commands into a Script
Version: 2022-04-29 | Licensed for Cyber School. | learning.lpi.org | 239
Let’s observe the output:
$ ./friendly2.sh Carol
Hello Carol!
$ ./friendly2.sh Carol Dave Henry
Hello Carol, and Dave, and Henry!
Using Regular Expressions to Perform Error Checking
It’s possible that we want to verify all arguments that the user is entering. For example, perhaps we
want to ensure that all names passed to friendly2.sh contain only letters, and any special characters
or numbers will cause an error. To perform this error checking, we will use grep.
Recall that we can use regular expressions with grep.
$ echo Animal | grep “^[A-Za-z]*$”
Animal
$ echo $?
0
$ echo 4n1ml | grep “^[A-Za-z]*$”
$ echo $?
1
The ^ and the $ indicate the beginning and end of the line respectively. The [A-Za-z] indicates a
range of letters, upper or lower case. The * is a quantifier, and modifies our range of letters so that
we are matching zero to many letters. In summary, our grep will succeed if the input is only letters,
and fails otherwise.
The next thing to note is that grep is returning exit codes based on whether there was a match or
not. A positive match returns 0, and a no match returns a 1. We can use this to test our arguments
inside our script.
Linux Essentials (Version 1.6) | Topic 3: The Power of the Command Line
240 | learning.lpi.org | Licensed for Cyber School. | Version: 2022-04-29
1 #!/bin/bash
2
3 # a friendly script to greet users
4
5 if [ $# -eq 0 ]
6 then
7 echo “Please enter at least one user to greet.”
8 exit 1
9 else
10 for username in $@
11 do
12 echo $username | grep “^[A-Za-z]*$” > /dev/null
13 if [ $? -eq 1 ]
14 then
15 echo “ERROR: Names must only contains letters.”
16 exit 2
17 else
18 echo “Hello $username!”
19 fi
20 done
21 exit 0
22 fi
On line 12, we are redirecting standard output to /dev/null, which is a simple way to suppress it.
We don’t want to see any output from the grep command, we only want to test its exit code, which
happens on line 13. Notice also that we are using an exit code of 2 to indicate an invalid argument. It
is generally good practice to use different exit codes to indicate different errors; in this way, a savvy
user can use these exit codes to troubleshoot.
$ ./friendly2.sh Carol Dave Henry
Hello Carol!
Hello Dave!
Hello Henry!
$ ./friendly2.sh 42 Carol Dave Henry
ERROR: Names must only contains letters.
$ echo $?
2
Linux Essentials (Version 1.6) | 3.3 Turning Commands into a Script
Version: 2022-04-29 | Licensed for Cyber School. | learning.lpi.org | 241
Guided Exercises
1. Read the contents of script1.sh below:
#!/bin/bash
if [ $# -lt 1 ]
then
echo “This script requires at least 1 argument.”
exit 1
fi
echo $1 | grep “^[A-Z]*$” > /dev/null
if [ $? -ne 0 ]
then
echo “no cake for you!”
exit 2
fi
echo “here’s your cake!”
exit 0
What is the output of these commands?
◦ ./script1.sh
◦ echo $?
◦ ./script1.sh cake
◦ echo $?
◦ ./script1.sh CAKE
◦ echo $?
Linux Essentials (Version 1.6) | Topic 3: The Power of the Command Line
242 | learning.lpi.org | Licensed for Cyber School. | Version: 2022-04-29
2. Read the contents of file script2.sh:
for filename in $1/*.txt
do
cp $filename $filename.bak
done
Describe the purpose of this script as you understand it.
Linux Essentials (Version 1.6) | 3.3 Turning Commands into a Script
Version: 2022-04-29 | Licensed for Cyber School. | learning.lpi.org | 243
Explorational Exercises
1. Create a script that will take any number of arguments from the user, and print only those
arguments which are numbers greater than 10.
Linux Essentials (Version 1.6) | Topic 3: The Power of the Command Line
244 | learning.lpi.org | Licensed for Cyber School. | Version: 2022-04-29
Summary
In this section, you learned:
• What exit codes are, what they mean, and how to implement them
• How to check the exit code of a command
• What for loops are, and how to use them with arrays
• How to use grep, regular expressions and exit codes to check user input in scripts.
Commands used in the exercises:
shift
This will remove the first element of an array.
Special Variables:
$?
Contains the exit code of the last command executed.
$@, $*
Contain all arguments passed to the script, as an array.
Linux Essentials (Version 1.6) | 3.3 Turning Commands into a Script
Version: 2022-04-29 | Licensed for Cyber School. | learning.lpi.org | 245
Answers to Guided Exercises
1. Read the contents of script1.sh below:
#!/bin/bash
if [ $# -lt 1 ]
then
echo “This script requires at least 1 argument.”
exit 1
fi
echo $1 | grep “^[A-Z]*$” > /dev/null
if [ $? -ne 0 ]
then
echo “no cake for you!”
exit 2
fi
echo “here’s your cake!”
exit 0
What is the output of these commands?
◦ Command: ./script1.sh
Output: This script requires at least 1 argument.
◦ Command: echo $?
Output: 1
◦ Command: ./script1.sh cake
Output: no cake for you!
◦ Command: echo $?
Output: 2
◦ Command: ./script1.sh CAKE
Output: here’s your cake!
Linux Essentials (Version 1.6) | Topic 3: The Power of the Command Line
246 | learning.lpi.org | Licensed for Cyber School. | Version: 2022-04-29
◦ Command: echo $?
Output: 0
2. Read the contents of file script2.sh:
for filename in $1/*.txt
do
cp $filename $filename.bak
done
Describe the purpose of this script as you understand it.
This script will make backup copies of all files ending with .txt in a subdirectory defined in the
first argument.
Linux Essentials (Version 1.6) | 3.3 Turning Commands into a Script
Version: 2022-04-29 | Licensed for Cyber School. | learning.lpi.org | 247
Answers to Explorational Exercises
1. Create a script that will take any number of arguments from the user, and print only those
arguments that are numbers greater than 10.
#!/bin/bash
for i in $@
do
echo $i | grep “^[0-9]*$” > /dev/null
if [ $? -eq 0 ]
then
if [ $i -gt 10 ]
then
echo -n “$i “
fi
fi
done
echo “”
