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

משימות מתוזמנות עם cron: להריץ סקריפט כל לילה בלינוקס בלי הפתעות

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

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

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

מה זה cron ומתי כדאי להשתמש בו?

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

משימות שמתאימות מאוד ל־cron:

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

לא תמיד cron הוא הבחירה הכי טובה. אם צריך תלות בין משימות, retry מתוחכם, תור עבודות, נעילות מבוזרות או ניטור עשיר — יש כלים אחרים. אבל בשביל “תריץ לי את זה כל לילה ב־02:15”, cron מצוין.

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

לפני שפותחים crontab -e, כדאי להכין ארבעה דברים:

  • סקריפט שרץ ידנית בלי שגיאות — לא מתחילים מהתזמון. קודם מריצים ביד.
  • נתיבים מוחלטים — במקום ./script.sh, משתמשים בנתיב מלא כמו /home/user/cron-demo/bin/nightly.sh.
  • לוגים — כל פלט ושגיאה צריכים להיכתב לקובץ.
  • סביבה מינימלית — לא מניחים ש־PATH, משתני סביבה או תיקיית העבודה יהיו כמו בטרמינל שלכם.

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

מבנה שורת cron: חמשת השדות שצריך להכיר

שורת cron רגילה נראית כך:

text
15 2 * * * /bin/bash /home/user/cron-demo/bin/nightly.sh

השורה הזאת אומרת: הרץ את הסקריפט בכל יום בשעה 02:15. חשוב להחליף את /home/user בנתיב האמיתי אצלכם. אם תעתיקו את השורה כמו שהיא בלי קובץ מתאים, תקבלו שגיאת “לא נמצא”.

שדה משמעות דוגמה
דקה 0 עד 59 15
שעה 0 עד 23 2
יום בחודש 1 עד 31 *
חודש 1 עד 12 *
יום בשבוע 0 עד 7, כאשר 0 ו־7 הם לרוב יום ראשון *

הכוכבית * אומרת “כל ערך אפשרי”. לכן 15 2 * * * פירושו: בדקה 15, בשעה 2, בכל יום, בכל חודש, בכל יום בשבוע.

דוגמה ראשונה: סקריפט bash שרץ ידנית וכותב לוג

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

bash
mkdir -p "$HOME/cron-demo/bin"
cat > "$HOME/cron-demo/bin/nightly.sh" <<'SH'
#!/usr/bin/env bash
set -euo pipefail

mkdir -p "$HOME/cron-demo/logs"
now="$(date '+%Y-%m-%d %H:%M:%S')"
echo "Nightly job ran at $now" >> "$HOME/cron-demo/logs/nightly.log"
echo "done"
SH

chmod +x "$HOME/cron-demo/bin/nightly.sh"
"$HOME/cron-demo/bin/nightly.sh"
tail -n 1 "$HOME/cron-demo/logs/nightly.log"

הפקודות יוצרות סקריפט בשם nightly.sh. ההרצה הידנית אמורה להדפיס done, ולאחר מכן שורה בסגנון Nightly job ran at 2026-10-26 02:15:00. התאריך והשעה יהיו כמובן לפי זמן ההרצה אצלכם. אם זה לא עובד ידנית, אין טעם עדיין להכניס את זה ל־cron.

חיבור הסקריפט ל־crontab

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

bash
tmp_file="$(mktemp)"
new_file="$(mktemp)"
home_dir="$HOME"

crontab -l > "$tmp_file" 2>/dev/null || true
grep -v 'cron-demo/bin/nightly.sh' "$tmp_file" > "$new_file" || true
printf '15 2 * * * /bin/bash %s/cron-demo/bin/nightly.sh >> %s/cron-demo/logs/cron.log 2>&1\n' "$home_dir" "$home_dir" >> "$new_file"

crontab "$new_file"
rm -f "$tmp_file" "$new_file"

crontab -l | grep 'cron-demo/bin/nightly.sh'

הפלט הצפוי הוא שורת cron אחת שמצביעה לסקריפט שלכם, למשל 15 2 * * * /bin/bash /home/user/cron-demo/bin/nightly.sh >> /home/user/cron-demo/logs/cron.log 2>&1. הסיומת >> ... 2>&1 חשובה: היא שומרת גם פלט רגיל וגם שגיאות בקובץ לוג, במקום לאבד אותן.

למה כדאי להריץ דרך /bin/bash?

גם אם לסקריפט יש shebang, כלומר שורה ראשונה כמו #!/usr/bin/env bash, בהרבה צוותים מעדיפים לכתוב ב־cron במפורש /bin/bash /path/to/script.sh. זה מפחית תקלות הרשאה ומבהיר מי מריץ את הסקריפט.

אם הסקריפט משתמש בתחביר ייחודי ל־bash, אל תסמכו על ברירת המחדל של cron. במערכות רבות היא /bin/sh, שלא תמיד תואמת ל־bash.

דוגמה שנייה: סקריפט Python לילי עם SQLite

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

python
mkdir -p "$HOME/cron-demo/bin"
cat > "$HOME/cron-demo/bin/daily_report.py" <<'PY'
from datetime import datetime, timedelta, timezone
from pathlib import Path
import sqlite3

base = Path.home() / "cron-demo"
db_path = base / "nightly.sqlite3"
base.mkdir(parents=True, exist_ok=True)

now = datetime.now(timezone.utc)

with sqlite3.connect(db_path) as conn:
    conn.execute("CREATE TABLE IF NOT EXISTS runs (id INTEGER PRIMARY KEY, ran_at TEXT NOT NULL)")
    conn.execute("INSERT INTO runs (ran_at) VALUES (?)", (now.isoformat(timespec="seconds"),))
    cutoff = (now - timedelta(days=7)).isoformat(timespec="seconds")
    conn.execute("DELETE FROM runs WHERE ran_at < ?", (cutoff,))
    count = conn.execute("SELECT COUNT(*) FROM runs").fetchone()[0]

print(f"stored run in {db_path}")
print(f"rows kept: {count}")
PY

python3 "$HOME/cron-demo/bin/daily_report.py"

הפלט הצפוי יהיה משהו כמו stored run in /home/user/cron-demo/nightly.sqlite3 ואחריו rows kept: 1. אם תריצו שוב, המספר יעלה. שימו לב לשימוש ב־? בשאילתת המחיקה: זו כתיבה פרמטרית ובטוחה יותר מהדבקת ערכים לתוך מחרוזת SQL. למי ששואל “מה זה SQL” בהקשר הזה: כאן SQL הוא השפה שבה אנחנו יוצרים טבלה, מכניסים רשומות, מוחקים ישנות וסופרים כמה נשארו.

כדי לתזמן את סקריפט ה־Python, עדיף להשתמש בנתיב מלא ל־python3. אפשר למצוא אותו עם command -v python3. שורת cron תיראה בערך כך:

text
30 2 * * * /usr/bin/python3 /home/user/cron-demo/bin/daily_report.py >> /home/user/cron-demo/logs/python.log 2>&1

השורה מריצה את הסקריפט בכל לילה ב־02:30 וכותבת לוג לקובץ python.log. החליפו את /usr/bin/python3 ואת /home/user לפי המחשב שלכם. אם אתם לומדים עכשיו פייתון למתחילים, זו דוגמה טובה למשימה אמיתית: קוד קצר, תוצאה ברורה, ולוג שאפשר לבדוק בבוקר.

דוגמה שלישית: בדיקת לוגים עם regex אחרי הרצה לילית

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

python
cat > "$HOME/cron-demo/bin/check_log.py" <<'PY'
from pathlib import Path
import re

log_path = Path.home() / "cron-demo" / "logs" / "nightly.log"
pattern = re.compile(r"^Nightly job ran at \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$")

if not log_path.exists():
    print("log file is missing")
    raise SystemExit(0)

matches = 0
for line in log_path.read_text(encoding="utf-8").splitlines():
    if pattern.match(line):
        matches += 1

print(f"valid nightly log lines: {matches}")
PY

python3 "$HOME/cron-demo/bin/check_log.py"

אם הרצתם את הדוגמה הראשונה לפחות פעם אחת, הפלט יהיה valid nightly log lines: 1 או מספר גדול יותר. אם הלוג עדיין לא קיים, תקבלו log file is missing. זו דרך פשוטה להשתמש ב־regex כדי לוודא שהפורמט נשאר יציב ולא נשבר בעקבות שינוי בסקריפט.

איך בודקים ש־cron באמת רץ?

הדרך הראשונה היא לבדוק את הלוג שהגדרתם בעצמכם:

  • tail -n 50 "$HOME/cron-demo/logs/cron.log"
  • tail -n 50 "$HOME/cron-demo/logs/python.log"
  • tail -n 50 "$HOME/cron-demo/logs/nightly.log"

הדרך השנייה היא לבדוק את לוגי המערכת. במערכות שונות זה משתנה: לעיתים /var/log/syslog, לעיתים /var/log/cron, ולעיתים משתמשים ב־journalctl. למשל, במערכת עם systemd אפשר לנסות journalctl -u cron או journalctl -u crond, תלוי בהפצה.

כדאי גם לבדוק שהשירות פעיל. באובונטו ודביאן השם הנפוץ הוא cron; בהפצות אחרות הוא עשוי להיות crond. הפקודה תהיה לרוב systemctl status cron או systemctl status crond.

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

/bin/sh: 1: python: not found

זו אחת התקלות הנפוצות ביותר. בטרמינל שלכם python אולי עובד, אבל ב־cron אין אותו ב־PATH, או שהמערכת משתמשת רק ב־python3. הפתרון: השתמשו ב־python3, ועדיף בנתיב מלא שמצאתם עם command -v python3.

/bin/sh: 1: /home/USER/cron-demo/bin/nightly.sh: Permission denied

הקובץ קיים, אבל אין לו הרשאת הרצה. תקנו עם chmod +x /home/USER/cron-demo/bin/nightly.sh. לחלופין, הריצו אותו דרך /bin/bash /home/USER/cron-demo/bin/nightly.sh, כפי שעשינו בדוגמאות.

/bin/sh: 1: cd: can't cd to /path/to/app

הנתיב לא קיים, או שהוא שונה בין המחשב שלכם לבין השרת. ב־cron אל תבנו על תיקיית עבודה נוכחית. השתמשו בנתיבים מוחלטים, ובדקו אותם עם ls -ld /path/to/app.

bad minute ואחריו errors in crontab file, can't install.

זו שגיאת תחביר בקובץ crontab. לרוב חסר שדה, יש תו לא חוקי, או שהדבקתם שורה שנשברה באמצע. ודאו שיש בדיוק חמישה שדות זמן לפני הפקודה. לדוגמה תקין: 15 2 * * * /bin/bash /home/user/script.sh.

No such file or directory

הסקריפט מחפש קובץ יחסי כמו data/input.csv, אבל ב־cron תיקיית העבודה אינה זו שחשבתם. הפתרון הוא לבנות נתיבים מתוך $HOME, מתוך מיקום הסקריפט, או להגדיר בתחילת הפקודה cd /home/user/project && ....

הגדרות סביבה שימושיות בתוך crontab

אפשר לשים בראש ה־crontab הגדרות שמקטינות הפתעות:

text
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=""

15 2 * * * /bin/bash /home/user/cron-demo/bin/nightly.sh >> /home/user/cron-demo/logs/cron.log 2>&1

SHELL מגדיר באיזה shell להשתמש, PATH מצמצם בעיות של פקודות שלא נמצאות, ו־MAILTO="" מבטל ניסיונות לשלוח פלט במייל מקומי. גם כאן צריך להחליף את /home/user בנתיב אמיתי.

שיקולי בטיחות קטנים שעושים הבדל

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

  • אל תשמרו סיסמאות בקוד. השתמשו במשתני סביבה או בקובץ הגדרות עם הרשאות מוגבלות.
  • אל תריצו כ־root בלי צורך. ברוב המקרים crontab של משתמש רגיל מספיק.
  • הגבילו מחיקות. אם יש פקודת rm, בדקו אותה פעמיים והתחילו במצב שמדפיס מה היה נמחק.
  • כתבו לוגים. משימה בלי לוג היא משימה שקשה מאוד לתקן.
  • בדקו עומס. אל תריצו עבודה כבדה בדיוק בשעה שבה יש גיבוי, סנכרון או עומס קבוע אחר.

סיכום: המתכון הקצר למשימה לילית יציבה

הדרך הבטוחה לעבוד עם cron היא לא לקפוץ ישר לתזמון. קודם כותבים סקריפט קטן וברור, מריצים אותו ידנית, מוסיפים לוג, משתמשים בנתיבים מוחלטים, ורק אז מוסיפים שורת crontab. אם מדובר ב־Python, מריצים עם python3 בנתיב מלא. אם יש SQL, משתמשים בפרמטרים ולא בשרשור מחרוזות. אם יש לוגים, אפשר אפילו לבדוק אותם עם regex.

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