کرون جاب (cron job) دستوری است که سرویس cron لینوکس در زمانهای مشخص و تکرارشونده خودکار اجرا میکند؛ مثل پشتیبانگیری هر شب ساعت ۲، پاککردن فایلهای موقت هر یکشنبه یا اجرای زمانبند لاراول هر دقیقه. برای ساختنش دستور crontab -e را میزنید و یک خط با این شکل اضافه میکنید: پنج فیلد زمان (دقیقه، ساعت، روز ماه، ماه، روز هفته) و بعد دستور. مثلاً 30 2 * * * /usr/local/bin/backup.sh یعنی هر روز ساعت ۲:۳۰ بامداد.
همین یک خط برای شروع کافی است، ولی بیشتر دردسرهای کرون جای دیگری است: اسکریپتی که در ترمینال درست کار میکند ولی در cron نه، خروجیای که معلوم نیست کجا رفت، یا دو اجرای همزمان که روی هم میافتند. در ادامه قالب زمان را با جدول مثال میبینیم، بعد سراغ همین دامها میرویم و در آخر systemd timer را معرفی میکنیم که برای بعضی کارها جایگزین بهتری است. مثالها برای Ubuntu 24.04 نوشته شدهاند.
قالب زمان crontab: پنج فیلد
┌───────────── دقیقه (0-59)
│ ┌─────────── ساعت (0-23)
│ │ ┌───────── روز ماه (1-31)
│ │ │ ┌─────── ماه (1-12)
│ │ │ │ ┌───── روز هفته (0-7؛ 0 و 7 هر دو یکشنبه)
│ │ │ │ │
* * * * * دستور
علامتهایی که در هر فیلد میشود استفاده کرد:
*یعنی «هر مقداری».,فهرست:1,15یعنی روز ۱ و ۱۵.-بازه:1-5یعنی دوشنبه تا جمعه./گام:*/10در فیلد دقیقه یعنی هر ۱۰ دقیقه.
جدول مثالها
| عبارت | معنی |
|---|---|
* * * * * |
هر دقیقه |
*/5 * * * * |
هر ۵ دقیقه |
0 * * * * |
سر هر ساعت |
30 2 * * * |
هر روز ساعت ۲:۳۰ |
0 9-17 * * 1-5 |
سر هر ساعت از ۹ تا ۱۷، دوشنبه تا جمعه |
0 0 * * 0 |
نیمهشب هر یکشنبه |
0 3 1 * * |
ساعت ۳ بامداد روز اول هر ماه |
15 4 * * 6 |
ساعت ۴:۱۵ هر شنبه |
0 */6 * * * |
هر ۶ ساعت (۰، ۶، ۱۲، ۱۸) |
@reboot |
یک بار بعد از هر بوت |
@daily |
معادل 0 0 * * * |
دو نکته که غافلگیرکنندهاند:
- روز هفته به سبک آمریکایی است.
0یکشنبه است و6شنبه. اگر کاری را برای «اول هفتهی ایرانی» میخواهید، شنبه یعنی6. - روز ماه و روز هفته با «یا» ترکیب میشوند، نه «و».
0 0 13 * 5یعنی روز سیزدهم هر ماه و همچنین هر جمعه، نه فقط جمعههایی که سیزدهماند.
ساعت کرون همان ساعت سیستم است. اگر سرور روی UTC است (که برای سرور انتخاب خوبی است)، 30 2 یعنی ۲:۳۰ به وقت UTC. با timedatectl منطقهی زمانی سرور را ببینید.
crontab -e و crontab -l
هر کاربر crontab شخصی خودش را دارد و جابهایش با دسترسی همان کاربر اجرا میشوند:
crontab -e # ویرایش crontab کاربر فعلی
crontab -l # نمایش crontab کاربر فعلی
sudo crontab -u deploy -l # نمایش crontab کاربر دیگر
بار اول که crontab -e میزنید، میپرسد با کدام ویرایشگر باز کند. بعد از ذخیره و خروج، cron خودش تغییرات را میخواند و لازم نیست سرویسی را ریاستارت کنید.
مراقب crontab -r باشید: کل crontab را بدون تأیید پاک میکند، و r روی صفحهکلید درست کنار e است. یک عادت خوب این است که هر از گاهی خروجی crontab -l را در فایلی نگه دارید، یا بهتر، جابهای مهم را در /etc/cron.d بگذارید که فایل است و میشود آن را در git نگه داشت.
/etc/cron.d: جابهای سیستمی با فیلد کاربر
فایلهای داخل /etc/cron.d همان قالب crontab را دارند، با یک تفاوت مهم: بعد از پنج فیلد زمان، یک فیلد اسم کاربر هم دارند.
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# زمانبند لاراول، هر دقیقه
* * * * * deploy cd /var/www/myapp && php artisan schedule:run >> /dev/null 2>&1
# پشتیبان شبانه
30 2 * * * root /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
چند قاعده که اگر رعایت نشوند فایل بیصدا نادیده گرفته میشود:
- مالک فایل باید root باشد و فقط root حق نوشتن داشته باشد (
chmod 644). - اسم فایل فقط حروف، رقم،
-و_داشته باشد. فایلی با نقطه در اسم (مثلmyapp.cronیاbackup.bak) در اوبونتو اجرا نمیشود. - خط آخر فایل باید با newline تمام شود.
مزیت /etc/cron.d این است که هر برنامه فایل خودش را دارد، با ابزارهای استقرار قابل نصب است و برای دیدن همهی جابها کافی است یک پوشه را نگاه کنید. پوشههای /etc/cron.daily، /etc/cron.weekly و مشابه هم هستند که اسکریپتهای اجرایی داخلشان با زمانبندی ثابت اجرا میشوند؛ برای کارهایی که زمان دقیقشان مهم نیست مناسباند.
چرا در ترمینال کار میکند و در cron نه؟
تقریباً همیشه جواب یکی از این سه است.
۱. PATH کوتاه است
cron فایلهایی مثل ~/.bashrc و ~/.profile را نمیخواند و PATH پیشفرضش فقط /usr/bin:/bin است. دستوری که در /usr/local/bin یا مسیری مثل ~/.local/bin است پیدا نمیشود. دو راه دارید: یا همه جا مسیر کامل بنویسید (/usr/local/bin/composer)، یا مثل مثال بالا، PATH را در بالای crontab تعریف کنید. متغیرهای دیگری که برنامه لازم دارد هم باید همینجا یا داخل خود اسکریپت تعریف شوند.
۲. شل sh است، نه bash
شل پیشفرض cron، /bin/sh است که در اوبونتو dash است. چیزهایی مثل [[ ... ]]، source یا آرایهها در آن کار نمیکنند. یا SHELL=/bin/bash را بالای فایل بگذارید، یا بهتر، منطق را در اسکریپتی با #!/bin/bash بنویسید و در cron فقط آن اسکریپت را صدا بزنید.
۳. علامت % معنای خاص دارد
در خط crontab، % یعنی «خط جدید» و هر چه بعد از اولین % بیاید بهعنوان ورودی به دستور داده میشود. این خط کار نمیکند:
0 3 * * * tar czf /backup/site-$(date +%F).tar.gz /var/www
باید % را با \ escape کنید:
0 3 * * * tar czf /backup/site-$(date +\%F).tar.gz /var/www
باز هم راه تمیزتر این است که این دستورها را در یک اسکریپت بگذارید؛ داخل اسکریپت % هیچ معنای خاصی ندارد.
خروجی و لاگ کرون جابها
cron خروجی هر جاب را برای کاربر صاحب آن ایمیل میکند، به شرطی که روی سرور برنامهی ارسال ایمیل نصب باشد. روی بیشتر سرورها نیست، پس خروجی و خطاها بیصدا دور ریخته میشوند. همیشه خروجی را صریح به جایی بفرستید:
30 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
>> به انتهای فایل اضافه میکند و 2>&1 خطاها را هم به همانجا میفرستد. اگر این لاگها بزرگ میشوند، برایشان یک فایل در /etc/logrotate.d بسازید.
برای اینکه ببینید cron اصلاً جاب را اجرا کرده یا نه، لاگ خود سرویس را نگاه کنید:
journalctl -u cron --since today
اگر خطی با CMD (...) برای جابتان میبینید، cron کارش را کرده و مشکل داخل خود دستور است. اگر هیچ خطی نیست، مشکل از قالب زمان، اسم فایل یا فیلد کاربر است.
یک ترفند ساده برای اشکالزدایی، اجرای اسکریپت در محیطی شبیه cron است:
env -i SHELL=/bin/sh PATH=/usr/bin:/bin HOME="$HOME" /bin/sh -c '/usr/local/bin/backup.sh'
جلوگیری از اجرای همزمان با flock
cron نمیداند اجرای قبلی تمام شده یا نه. اگر جابی هر ۵ دقیقه اجرا شود و یک بار ۷ دقیقه طول بکشد، دو نسخه همزمان اجرا میشوند؛ برای پشتیبانگیری یا همگامسازی، این یعنی فایلهای خراب یا کار تکراری. ابزار flock (که در اوبونتو نصب است) این را حل میکند:
*/5 * * * * deploy /usr/bin/flock -n /run/lock/myapp-sync.lock /usr/local/bin/sync.sh >> /var/log/myapp-sync.log 2>&1
flock پیش از اجرای دستور روی فایل قفل میگیرد. -n یعنی اگر قفل دست کس دیگری بود منتظر نمان و بیسروصدا خارج شو. قفل با تمامشدن دستور، حتی اگر با خطا تمام شود، خودبهخود آزاد میشود و فایل قفل جامانده دردسری درست نمیکند.
جایگزین: systemd timer
اوبونتو از systemd استفاده میکند و systemd زمانبند خودش را دارد. برای هر کار دو فایل میسازید: یک .service که میگوید چه اجرا شود و یک .timer که میگوید کی.
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
User=root
ExecStart=/usr/local/bin/backup.sh
[Unit]
Description=Run backup every night at 02:30
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=5min
[Install]
WantedBy=timers.target
فعالسازی و بررسی:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers
systemctl list-timers برای هر timer نشان میدهد آخرین بار کی اجرا شده و نوبت بعدی کی است. برای تست، سرویس را بدون صبر برای زمانش دستی اجرا کنید و لاگش را ببینید:
sudo systemctl start backup.service
journalctl -u backup.service -n 50
قالب OnCalendar با cron فرق دارد، ولی خواناتر است: Mon..Fri *-*-* 09:00:00 یعنی روزهای کاری ساعت ۹، و hourly، daily و weekly هم پذیرفته میشوند. اگر مطمئن نیستید عبارتی چه معنایی دارد، systemd-analyze زمانهای بعدیاش را حساب میکند:
systemd-analyze calendar "Sat *-*-* 04:15:00" --iterations=3
cron یا systemd timer؟
| cron | systemd timer | |
|---|---|---|
| راهاندازی | یک خط | دو فایل |
| لاگ | باید دستی هدایت شود | خودکار در journal |
| اجرای همزمان | با flock جلوگیری میشود |
تا اجرای قبلی تمام نشده، دوباره اجرا نمیشود |
| اجرای جامانده (سرور خاموش بوده) | ندارد | با Persistent=true بعد از روشنشدن اجرا میشود |
| محدودیت منابع، وابستگی به سرویسهای دیگر | ندارد | همهی امکانات systemd |
برای کارهای ساده و سریع، cron هنوز کوتاهترین راه است. برای کارهای مهم مثل پشتیبانگیری که لاگ، جلوگیری از همپوشانی و اجرای جامانده برایشان مهم است، systemd timer انتخاب بهتری است.
جمعبندی
- پنج فیلد: دقیقه، ساعت، روز ماه، ماه، روز هفته. یکشنبه
0است و شنبه6. crontab -eبرای جابهای شخصی؛/etc/cron.dبرای جابهای سیستمی، با فیلد کاربر و اسم فایل بدون نقطه.- cron پروفایل شل را نمیخواند: PATH را تعریف کنید، مسیر کامل بنویسید و
%را escape کنید. بهتر از همه، کار را در یک اسکریپت بگذارید. - خروجی را همیشه به فایل لاگ بفرستید و برای جابهای تکراری از
flockاستفاده کنید. - برای کارهای حساس، systemd timer لاگ، جلوگیری از همپوشانی و اجرای جامانده را بدون زحمت اضافه به شما میدهد.
اگر میخواهید از همین حالا یک جاب واقعی بسازید، پشتیبانگیری شبانه بهترین شروع است؛ در پشتیبانگیری با قاعدهی ۳-۲-۱ گفتهایم چه چیزی را کجا نگه دارید. برای کارهای پسزمینهی داخل خود برنامه هم صفها در لاراول را ببینید.