דילוג לתוכן
אוטומציה וסקריפטים

סקריפטים ב־Bash לאוטומציה: משתנים, לולאות ותנאים בלי להסתבך

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

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

מה צריך להכין מראש

הדוגמאות במאמר מיועדות ל־Linux/macOS עם Bash. ברוב מערכות לינוקס Bash כבר מותקן. ב־macOS הוא קיים, אך לעיתים בגרסה ישנה יותר; הדוגמאות כאן נשארות בסיסיות בכוונה כדי לעבוד גם שם.

  • טרמינל פתוח.
  • עורך טקסט פשוט, למשל nano, vim או עורך גרפי.
  • יכולת להריץ פקודות בסיסיות כמו pwd, ls, cd.
  • תיקייה זמנית לתרגול. מומלץ לא להריץ סקריפטים ראשונים בתוך תיקיות עבודה חשובות.

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

למה Bash טוב לאוטומציה יומיומית

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

  • סריקה של קבצים לפי סיומת.
  • בדיקת מצב דיסק או תיקייה.
  • חיפוש תבניות עם grep ו־regex.
  • יצירת גיבוי קטן לפני שינוי קבצים.
  • הרצת פקודות Git כמו git stash או git rebase כחלק מרצף עבודה מקומי, בזהירות ובהבנה.

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

משתנים ב־Bash: הכל מתחיל בשם וערך

משתנה ב־Bash מוגדר בלי רווחים סביב סימן השוויון. זו אחת הטעויות הראשונות והנפוצות ביותר. כותבים name="backup", לא name = "backup".

פעולה דוגמה הערה
הגדרת משתנה folder="logs" אין רווחים סביב =
שימוש במשתנה "$folder" כמעט תמיד לעטוף במרכאות
ערך ברירת מחדל ${1:-80} אם לא התקבל ארגומנט, השתמש ב־80
חישוב מספרי $((count + 1)) מתאים לחישובים שלמים פשוטים

דוגמה 1: העתקת קבצי טקסט לתיקיית גיבוי

הדוגמה הבאה מדגימה משתנים, לולאת for, בדיקת קיום קובץ, ושימוש ב־basename. היא יוצרת סביבת תרגול זמנית, מעתיקה רק קבצי .txt, ומדפיסה מה הועתק.

bash
#!/usr/bin/env bash
set -u

tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT

mkdir -p "$tmpdir/inbox" "$tmpdir/backup"

printf 'monthly report\n' > "$tmpdir/inbox/report.txt"
printf 'meeting notes\n' > "$tmpdir/inbox/notes.md"

prefix="demo"
copied=0

for file in "$tmpdir"/inbox/*.txt; do
  [ -e "$file" ] || continue

  base=$(basename "$file")
  cp "$file" "$tmpdir/backup/${prefix}_${base}"
  copied=$((copied + 1))
done

echo "Copied $copied file(s)"

for file in "$tmpdir"/backup/*; do
  [ -e "$file" ] || continue
  basename "$file"
done

כדי להריץ, שמרו את הקוד בקובץ למשל backup-demo.sh, ואז הריצו bash backup-demo.sh. הפלט הצפוי הוא:

text
Copied 1 file(s)
demo_report.txt

שימו לב לשורה [ -e "$file" ] || continue. היא מגינה עלינו ממקרה שבו אין קבצי .txt בתיקייה. בלי הבדיקה הזאת, התבנית עצמה עלולה להישאר כמחרוזת ולהוביל לתוצאות מבלבלות.

תנאים: לקבל החלטות בתוך הסקריפט

תנאי ב־Bash נכתב לרוב עם if, בדיקה בתוך [ ] או [[ ]], ואז then. התנאי מאפשר לסקריפט לא רק לבצע פקודות, אלא לבחור מה לעשות לפי מצב המערכת, קלט מהמשתמש או תוצאה של פקודה קודמת.

בדיקות נפוצות:

  • [ -f "$file" ] — האם זה קובץ רגיל.
  • [ -d "$dir" ] — האם זו תיקייה.
  • [ "$count" -gt 0 ] — האם מספר גדול מאפס.
  • [[ "$name" == *.log ]] — האם מחרוזת מתאימה לתבנית.

דוגמה 2: בדיקת שימוש בדיסק עם ערך ברירת מחדל

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

bash
#!/usr/bin/env bash
set -u

limit="${1:-80}"

usage=$(df -P . | awk 'NR == 2 { gsub("%", "", $5); print $5 }')

if [ "$usage" -ge "$limit" ]; then
  echo "Disk usage is high: ${usage}% (limit: ${limit}%)"
else
  echo "Disk usage is OK: ${usage}% (limit: ${limit}%)"
fi

שמרו כ־disk-check.sh והריצו למשל bash disk-check.sh 90. הפלט תלוי במחשב שלכם, אבל ייראה כך:

text
Disk usage is OK: 42% (limit: 90%)

או במקרה אחר:

text
Disk usage is high: 91% (limit: 90%)

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

לולאות: לבצע אותה פעולה על הרבה פריטים

לולאות הן הלב של הרבה סקריפטים. במקום להריץ אותה פקודה על עשרה קבצים ידנית, נותנים ל־Bash לעבור עליהם. יש כמה סוגים, אבל בפועל תשתמשו הרבה ב־for וב־while read.

  • for מתאים לרשימה פשוטה של קבצים, מילים או ערכים.
  • while IFS= read -r line מתאים לקריאה בטוחה של שורות מקובץ או פלט של פקודה.
  • כמעט תמיד כדאי לעטוף משתנים במרכאות: "$file".

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

דוגמה 3: סריקת לוגים עם regex וספירת אזהרות

כאן נשתמש ב־grep -E כדי לחפש תבנית regex פשוטה: שורות שמכילות ERROR או WARN. הסקריפט יוצר שני קובצי לוג זמניים, סורק אותם, ומדפיס רק קבצים שיש בהם התאמות.

bash
#!/usr/bin/env bash
set -u

tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT

mkdir -p "$tmpdir/logs"

cat > "$tmpdir/logs/app.log" <<'LOG'
INFO application started
WARN cache is almost full
ERROR failed to load optional file
LOG

cat > "$tmpdir/logs/worker.log" <<'LOG'
INFO worker started
INFO job completed
LOG

pattern='(ERROR|WARN)'

while IFS= read -r file; do
  count=$(grep -E -c "$pattern" "$file" || true)

  if [ "$count" -gt 0 ]; then
    echo "$(basename "$file"): $count"
  fi
done < <(find "$tmpdir/logs" -type f -name "*.log" | sort)

הרצה עם bash log-scan.sh תחזיר:

text
app.log: 2

הקובץ worker.log לא מודפס כי אין בו שורות שמתאימות לתבנית. השימוש ב־|| true אחרי grep חשוב במיוחד כאשר עובדים עם set -e, כי grep מחזיר קוד יציאה שאינו אפס כאשר אין התאמות. זה לא בהכרח כישלון; לפעמים זו תוצאה תקינה לגמרי.

איך לבנות סקריפט Bash שלב אחר שלב

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

  1. מריצים את הפקודה ידנית בטרמינל על דוגמה קטנה.
  2. שומרים אותה בקובץ .sh.
  3. מחליפים ערכים קבועים במשתנים.
  4. מוסיפים תנאי שמוודא שהתיקייה או הקובץ קיימים.
  5. מוסיפים לולאה רק אחרי שהפקודה עובדת על פריט אחד.
  6. מדפיסים מה הסקריפט עומד לעשות לפני שמבצעים פעולה שמשנה קבצים.

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

הרשאות והרצה: bash script.sh מול ./script.sh

יש שתי דרכים נפוצות להריץ סקריפט:

  • bash script.sh — מריץ את הקובץ באמצעות Bash גם בלי הרשאת הרצה.
  • ./script.sh — דורש הרשאת הרצה ושורת shebang תקינה, למשל #!/usr/bin/env bash.

כדי לאפשר הרצה ישירה:

bash
chmod +x script.sh
./script.sh

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

טעויות נפוצות והודעות שגיאה אמיתיות

Permission denied

אם הרצתם ./script.sh וקיבלתם:

text
bash: ./script.sh: Permission denied

הקובץ כנראה לא קיבל הרשאת הרצה. פתרון:

bash
chmod +x script.sh
./script.sh

או פשוט:

bash
bash script.sh

bad interpreter: No such file or directory

הודעה נפוצה אחרי עריכה ב־Windows:

text
bash: ./script.sh: /usr/bin/env: bad interpreter: No such file or directory

או לעיתים תראו סימן ^M בתוך ההודעה. המשמעות היא שהקובץ נשמר עם סיומות שורה של Windows. אפשר לתקן כך:

bash
sed -i 's/\r$//' script.sh

ב־macOS הפקודה מעט שונה:

bash
sed -i '' 's/\r$//' script.sh

unary operator expected

שגיאה כזו:

text
./check.sh: line 7: [: -gt: unary operator expected

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

bash
if [ $count -gt 0 ]; then
  echo "found"
fi

כתבו בצורה בטוחה יותר:

bash
count="${count:-0}"

if [ "$count" -gt 0 ]; then
  echo "found"
fi

syntax error near unexpected token `then'

אם כתבתם את התנאי בשורה אחת בלי נקודה־פסיק, למשל:

bash
if [ "$count" -gt 0 ] then
  echo "found"
fi

תקבלו שגיאת תחביר. תקנו לאחת משתי הצורות:

bash
if [ "$count" -gt 0 ]; then
  echo "found"
fi

או:

bash
if [ "$count" -gt 0 ]
then
  echo "found"
fi

הרגלים קטנים שעושים סקריפטים טובים יותר

  • השתמשו בשמות ברורים: backup_dir עדיף על bd.
  • עטפו משתנים במרכאות: "$file" במקום $file.
  • התחילו בפעולות קריאה בלבד: בדיקה, הדפסה, ספירה. רק אחר כך שינוי.
  • בדקו כל שלב בנפרד: אם find מחזיר רשימה לא נכונה, הלולאה רק תגדיל את הבעיה.
  • הכירו כלי בדיקה: shellcheck יכול לזהות הרבה טעויות Bash נפוצות לפני הרצה.

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

סיכום: לא לבנות מחדש, אלא לשדרג את העבודה היומיומית

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

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