Bash Tips
997 subscribers
14 photos
4 files
45 links
רוצים להשתמש בלינוקס אבל לא ממש מכירים את הכלים שהיא מספקת לעבודה?

בערוץ הבא תמצאו אוסף טיפים שימושיים ב-Bash והכרות עם כלים שונים שעשויים לחסוך מאמץ ועבודה בכתיבת סקריפטים ומימוש אוטומציות.
Download Telegram
לוג לפונקציות, צנזור מידע ו subshell
רמת קושי: #advanced

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

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

get_page(){
logger "run function: ${FUNCNAME[0]} with parameters: ${*}"
echo "${page}"
}

וכך נראית הסקריפט כשאנו מריצים אותו

$ bash test.sh https://google.com
[*] run function: get_page with parameters: https://google.com

ולמי שמאוד סקרן כך נראית פונקציית logger

logger() {
status=$?
if [[ $status -eq 0 ]]; then
echo -e "\e[92m[*] ${@}"
else
echo -e "\e[91m[!] ${@}"
fi
}

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

$ test.sh https://admin.site myuser mypass
[*] run function: get_page with parameters: https://admin.site myuser mypass

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

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

get_page(){
input=$(sed "s|${REPLACE}|${REPLACE_TO}|g" <<<"${@}")
logger "run function: ${FUNCNAME[0]} with parameters: ${input}"
echo "${page}"
}

וכך אנו קוראים לפונקציה בסקריפט

REPLACE="myp.*"
REPLACE_TO="***"
get_page "${@}"

נבדוק רגע נראה שהכל עובד תקין

$ test.sh https://admin.site myuser mypass
[*] run function: get_page with parameters: https://admin.site myuser ***

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

REPLACE="myp.*"
REPLACE_TO="***"
get_page "${@}"
unset REPLACE
unset REPLACE_TO

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

(
REPLACE="myp.*"
REPLACE_TO="***"
page=$(get_page "${@}")
)

#scripting
#subshell
#function

@bash_tips
עושים שיפט
רמת קושי: #advanced

אנו מכירים את צורת העברת פרמטרים לסקריפט באש, כל פרמטר מקבל מספר וניתן לגשת לפרמטרים לפי האינדקס שלהם על ידי $
$ cat test.sh
echo $1
echo $2
echo $3

$ bash test.sh one two three
one
two
three

לעיתים צורת העברה של פרמטרים לפי אינדקס יוצרת לנו בעיה, כי לא תמיד נרצה להעביר את כל הפרמטרים
לדוגמה אנו רוצים לבנות סקריפט שמדפיס את כל הפרמטרים שהוא מקבל, לשם כך נשתמש ב $*

$ cat test.sh
echo "$*"

$ bash test.sh one two three
one two three


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

$ bash test.sh base64 one two three
base64 one two three

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

action=$1
echo "${*:2}"


דרך נקייה יותר לעשות את אותו הדבר היא להשתמש בפקודת shift
פקודת shift היא כלי שמגיע כחלק מ bash builtin ומה שהיא נותנת זה "להזיז" את הפרמטרים מספר צעדים קדימה כך שהפרמטר הראשון יוסר מהרשימה ומה שהיה לפני כן במשתנה $2 יהפוך להיות $1

$ cat test.sh
shift 1
echo $1
echo $2
echo $3

$ bash test.sh one two three
two
three

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

action=$1
shift 1
echo "${*}"

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


#shift
#scripting
#function
#parameters

@bash_tips
להטמיע בינרי בסקריפט באש
רמת קושי: #advanced

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

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

כעת נראה איך הרעיון יכול להיות ממומש הלכה למעשה
לצורך העניין כך נראה הסקריפט שלנו

$ cat -n /tmp/installer.sh
1 #!bin/bash
2
3 binary_content=$(awk '/^ARCHIVE_HERE/ {print FNR + 1}' $0)
4
5 tail -n+$binary_content $0 | base64 -d | tar -ztv
6
7 exit 0
8
9 ARCHIVE_HERE
10


מה שקורה כאן הוא כך
שורה 3: עוברת על כל הסקריפט ומחפשת את העוגן שהגדרנו, במקרה שלנו העוגן נמצא בשורה 9, ARCHIVE_HERE, למי שלא מכיר פרמטר $0 הוא בעצם שם הסקריפט

שורה 5: לאחר שאנו יודעים מאיזה שורה מתחיל התוכן הבינרי, אנו שומרים רק את התוכן הבינרי לתוך משתנה, הדבר נעשה על ידי פקודת tail שקוראת תוכן מסוף הקובץ ומקבלת שורה ספציפית מהיכן לקרוא, התוכן עובר ל base64 במקרה שלנו ולכן נקודד אותו חזרה לתוכן רגיל, פקודת tar תציג לנו את המידע שהוטמע בפנים

שורה 9: זהו העוגן שהגדרנו

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


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

$ tar -tvf installer.tar
installer/
installer/req_list
installer/installer.db
installer/execfile


ניתן להשתמש ב cat כדי לשפוך את התוכן הבינרי לקובץ אבל ישנם המון מקומות בהן התוכן עשוי להשתנות ולא להישאר נאמן למקור לכן נעדיף לעבוד עם base64

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

9 ARCHIVE_HERE
10


כעת מהטרמיל אנו מקודדים את הקובץ לbase64, ומשרשרים את הפלט לסוף הסקריפט זה נראה כך

$ base64 installer.tar >> installer.sh

אנו משתמשים באופרטור >> כדי להוסיף את התוכן לסוף הקובץ ולכן השורה הריקה שהשארנו מקודם תתמלא בתוכן החדש

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

$ bash installer.sh
installer/
installer/req_list
installer/installer.db
installer/execfile


ניתן כמובן לשנות את שורה 5 וליצור קובץ ישירות מהתוכן שיוצר השחזור של ה base64

#tar
#binary
#base46
#scripting

@bash_tips
איך לכתוב באש ומתי
רמת קושי: #beginners

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

לדוגמה

# Use this

if (( my_var > 3 )); then
do_something
fi

if [[ "${my_var}" -gt 3 ]]; then
do_something
fi


# Instead of this
# Probably unintended lexicographical comparison.
if [[ "${my_var}" > 3 ]]; then
# True for 4, false for 22.
do_something
fi


הדף מופיע כחלק מפרויקט רחב יותר של גוגל בשם Google Style Guides שם תוכלו למצוא עוד המון הערות בקשר לשפות אחרות כמו go, python, c וכו'


#shellguide
#scripting
#rules

@bash_tips
אוטומציה ל telnet
רמת קושי: #advanced

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

$ telnet localhost 25 
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 My Mail Server

התחברנו לשרת ה SMTP דרך telnet ומשלב זה שורת הפקודה (עדין telnet) ממתינה שנשלח פקודות לשירות אליו התחברנו, במקרה שלנו פקודות לשירות SMTP עד שנעביר את הפקודה quit שתסגור את החיבור.

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

יש 2 דרכים עיקריים לביצוע אוטומציה ל telnet
הראשונה היא על ידי כלי אוטומציה של באש בשם expect עליו לא נדבר היום
האופציה השניה היא על ידי העברת ערכים ל stdin של telnet

echo "ehlo test" | telnet localhost 25

להלן דוגמה לסקריפט ששולח מייל על ידי telnet

{
echo "ehlo test"
sleep 1
echo "mail from:meir@any.domain"
echo "rcpt to:fpk1ezqg@my.domain"
echo "data"
sleep 1
echo "subject: bla bla bla"
echo "more bla bla bla"
echo "."
sleep 1
echo "quit"
} | telnet localhost 25

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


#telnet
#scripting

@bash_tips