توی این درس یاد میگیری سرور باید کارهای تکراری (بکاپ، پاکسازی، گزارش) را خودش و سر وقت انجام بدهد. ابزار کلاسیک این کار cron است: هر کاربر یک جدول زمانبندی (crontab) دارد که هر خطش یک زمان پنجقسمتی (دقیقه، ساعت، روز ماه، ماه، روز هفته) و یک دستور است. با crontab -e ویرایش و با crontab -l مرورش میکنی؛ میفهمی کارهای cron در یک محیط محدود اجرا میشوند (و چرا بسیاری از «در ترمینال کار میکند ولی در cron نه»ها از همینجاست)، خروجی و لاگشان را با grep CRON /var/log/syslog پیدا میکنی، و با systemd timer آشنا میشوی که جایگزین امروزی آن است. تمرین: هر شب ساعت ۲ یک بکاپ خودکار بگیر.
مسئله: «هر شب ساعت ۲ این کار را بکن»
Section titled “مسئله: «هر شب ساعت ۲ این کار را بکن»”اگر باید هر شب بکاپ گرفته شود، هر هفته لاگهای قدیمی پاک شود و هر ساعت یک گزارش ساخته شود، نمیشود آدم ساعت ۲ شب بیدار بماند. به یک زمانبند نیاز داری: برنامهای که همیشه روشن است، ساعت را زیر نظر دارد و وقتش که رسید، دستور را اجرا میکند.
تشبیه: زنگ ساعت دیواری با دفترچهی برنامه
Section titled “تشبیه: زنگ ساعت دیواری با دفترچهی برنامه”cron مثل یک زنگ ساعت دیواری است که هر دقیقه یک نگاه به دفترچهی برنامه میاندازد: «ساعت ۲:۰۰ هر شب: بکاپ؛ هر ۵ دقیقه: بررسی سلامت». هر دقیقه خطوطی را که با زمان الآن میخوانند اجرا میکند. این زنگ حافظهی گذشته ندارد: اگر سرور ساعت ۲:۰۰ خاموش بوده باشد، آن اجرا از دست رفته و بعداً جبران نمیشود (برای همین بعضی کارها به anacron یا systemd timer با Persistent=true نیاز دارند).
مثالهای عملی
Section titled “مثالهای عملی”این درس روی ماشین systemdدار آزمایشی (که cron و rsyslog واقعی دارد) اجرا شده است. cron کمترین واحد زمانش یک دقیقه است، پس در مثالها مجبورم بین ساختن یک کار و دیدن اثرش تا یکی دو دقیقه صبر کنم (در خروجیها فقط نتیجه را میبینی). برای کاربر alice یک crontab شخصی میسازیم.
مثال ۱: ساختار پنجقسمتی
Section titled “مثال ۱: ساختار پنجقسمتی”هر خط crontab این شکل را دارد: پنج فیلد زمان و بعد دستور:
┌──────── دقیقه (0-59)│ ┌────── ساعت (0-23)│ │ ┌──── روز ماه (1-31)│ │ │ ┌── ماه (1-12)│ │ │ │ ┌ روز هفته (0-7؛ 0 و 7 = یکشنبه، 1 = دوشنبه)│ │ │ │ │* * * * * دستورهر فیلد میتواند یکی از این شکلها باشد:
| شکل | معنی | نمونه |
|---|---|---|
* |
هر مقدار | * * * * *: هر دقیقه |
| عدد | همان مقدار | 30 8 * * *: هر روز ۸:۳۰ |
a,b |
فهرست | 0 9,17 * * *: ساعت ۹ و ۱۷ |
a-b |
بازه | 0 9 * * 1-5: ۹ صبح دوشنبه تا جمعه |
*/n |
هر n واحد | */5 * * * *: هر ۵ دقیقه |
a-b/n |
بازه با قدم | 0 8-18/2 * * *: هر ۲ ساعت از ۸ تا ۱۸ |
و چند میانبر:
| میانبر | معادل |
|---|---|
@reboot |
یک بار بعد از هر بوت |
@hourly |
0 * * * * |
@daily |
0 0 * * * |
@weekly |
0 0 * * 0 |
@monthly |
0 0 1 * * |
یک اشتباه رایج: * 2 * * * هر شب ساعت ۲ نیست! دقیقه * است، یعنی «هر دقیقهی ساعت ۲»: ۶۰ بار در شب. برای یک بار باید دقیقه را بنویسی: 0 2 * * *. (در بخش systemd timer با ابزاری که زمانها را حساب میکند ثابتش میکنم.)
مثال ۲: crontab -e و crontab -l
Section titled “مثال ۲: crontab -e و crontab -l”crontab -e جدول خودت را با یک ویرایشگر باز میکند و crontab -l آن را فهرست میکند. (crontab -r همه را پاک میکند؛ مثال اشتباهات رایج.) اول کاربر را میسازم و دو تابع ترمینال مجازی را:
useradd -m -s /bin/bash alicerun_in_tmux() { tmux -u kill-server 2>/dev/null; tmux -u new-session -d -s t -x 100 -y "${TH:-26}" "$@"; sleep 1; }screen() { tmux capture-pane -t t -p | sed -e :a -e '/^[[:space:]]*$/{$d;N;ba' -e '}'; }echo "--- هنوز جدولی ندارد:"su - alice -c 'crontab -l' 2>&1echo "--- ساعت و منطقهی زمانی این سرور (cron با همین کار میکند):"date--- هنوز جدولی ندارد:no crontab for alice--- ساعت و منطقهی زمانی این سرور (cron با همین کار میکند):Sun Oct 4 07:55:59 UTC 2026حالا crontab -e، در ویرایشگر nano (با EDITOR=nano، وگرنه اولین بار انتخاب ویرایشگر میپرسد) با ترمینال مجازی:
TH=28run_in_tmux "su - alice"tmux send-keys -t t "export EDITOR=nano; crontab -e" Enter; sleep 2screen GNU nano 7.2 /tmp/crontab.AhpOh2/crontab# Edit this file to introduce tasks to be run by cron.## Each task to run has to be defined through a single line# indicating with different fields when the task will be run# and what command to run for the task## To define the time you can provide concrete values for# minute (m), hour (h), day of month (dom), month (mon),# and day of week (dow) or use '*' in these fields (for 'any').## Notice that tasks will be started based on the cron's system# daemon's notion of time and timezones.## Output of the crontab jobs (including errors) is sent through# email to the user the crontab file belongs to (unless redirected).## For example, you can run a backup of all your user accounts# at 5 a.m every week with:# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/## For more information see the manual pages of crontab(5) and cron(8)## m h dom mon dow command
[ Read 23 lines ]^G Help ^O Write Out ^W Where Is ^K Cut ^T Execute ^C Location M-U Undo^X Exit ^R Read File ^\ Replace ^U Paste ^J Justify ^/ Go To Line M-E Redoویرایشگر یک فایل موقت با چند خط توضیح (که با # شروع میشوند و نادیده گرفته میشوند) نشان میدهد. میروم انتهای فایل (Alt+/ در nano) و یک خط مینویسم: هر دقیقه، تاریخ و ساعت را به یک فایل اضافه کن. دقت کن % را با \ escape کردهام (دلیلش در اشتباهات رایج):
tmux send-keys -t t M-/; sleep 1tmux send-keys -t t -l "* * * * * date '+\%F \%T' >> /tmp/cron-hello.log 2>&1"; tmux send-keys -t t Enter; sleep 1tmux send-keys -t t C-o; sleep 1; tmux send-keys -t t Enter; sleep 1; tmux send-keys -t t C-x; sleep 2tmux send-keys -t t "crontab -l | grep -v '^#'" Enter; sleep 1screen | tail -5tmux -u kill-servercrontab: installing new crontabalice@server:~$ crontab -l | grep -v '^#'* * * * * date '+\%F \%T' >> /tmp/cron-hello.log 2>&1
alice@server:~$بعد از ذخیره، crontab پیام installing new crontab را داد و crontab -l خط را نشان میدهد (بدون خطهای توضیح). حالا فقط باید صبر کنیم تا cron دقیقهی بعد بیدار شود. من دو دقیقه صبر میکنم و فایل را میخوانم:
sleep 125echo "--- فایل خروجی کار (هر دقیقه یک خط):"cat /tmp/cron-hello.logecho "--- لاگ خود cron در syslog:"grep CRON /var/log/syslog | grep alice | grep -v 'No MTA' | tail -2 | sed -E 's/^[^ ]+ server //'--- فایل خروجی کار (هر دقیقه یک خط):2026-10-04 07:57:012026-10-04 07:58:01--- لاگ خود cron در syslog:CRON[16938]: (alice) CMD (date '+%F %T' >> /tmp/cron-hello.log 2>&1)CRON[16941]: (alice) CMD (date '+%F %T' >> /tmp/cron-hello.log 2>&1)دو (یا بیشتر) خط با فاصلهی دقیق یک دقیقه: کار در ابتدای هر دقیقه (ثانیهی ۰۰ تا ۰۱) اجرا شد. و هر اجرا در syslog با کاربر و دستور ثبت شد: CRON[pid]: (alice) CMD (...)، اولین جایی که برای دیدن «cron این کار را اجرا کرد یا نه» میگردی.
خط «0 2 * * *» چه زمانی اجرا میشود؟
دقیقه ۰ و ساعت ۲ و بقیه * (هر روز/ماه/روز هفته). اگر دقیقه * بود (* 2 * * *)، در تمام ۶۰ دقیقهی ساعت ۲ اجرا میشد.
مثال ۳: محیط محدود کارهای cron
Section titled “مثال ۳: محیط محدود کارهای cron”کار cron در ترمینال تو اجرا نمیشود. یک shell بیورودی و بدون ترمینال است که فایلهای ~/.bashrc و ~/.profile را نمیخواند، متغیرهای shell تو را ندارد و پوشهی جاریاش پوشهی خانه است. ببین چه چیزهایی میبیند. این بار crontab را از یک فایل میسازم (crontab فایل، که کل جدول را عوض میکند) با چند کار آزمایشی:
cat > /tmp/cron-a.tab <<'EOF'* * * * * env | sort > /tmp/cron-env.txt* * * * * echo "SHELL=$SHELL RANDOM=[$RANDOM] BASH_VERSION=[$BASH_VERSION] cwd=$(pwd)" > /tmp/cron-shell.txt* * * * * ls /no/such/dir >> /tmp/cron-err.log 2>&1* * * * * ls /no/such/dirEOFsu - alice -c 'crontab /tmp/cron-a.tab && crontab -l'echo "(صبر برای اجرای دقیقهی بعد...)"sleep 65echo "--- متغیرهای محیطی کار cron:"cat /tmp/cron-env.txtecho "--- کدام shell، و پوشهی جاری؟"cat /tmp/cron-shell.txt* * * * * env | sort > /tmp/cron-env.txt* * * * * echo "SHELL=$SHELL RANDOM=[$RANDOM] BASH_VERSION=[$BASH_VERSION] cwd=$(pwd)" > /tmp/cron-shell.txt* * * * * ls /no/such/dir >> /tmp/cron-err.log 2>&1* * * * * ls /no/such/dir(صبر برای اجرای دقیقهی بعد...)--- متغیرهای محیطی کار cron:HOME=/home/aliceLANG=C.UTF-8LOGNAME=alicePATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/binPWD=/home/aliceSHELL=/bin/sh--- کدام shell، و پوشهی جاری؟SHELL=/bin/sh RANDOM=[] BASH_VERSION=[] cwd=/home/aliceنتیجه:
- محیط خیلی کوچک است: فقط چند متغیر (
HOME،LOGNAME،PATH،SHELL،LANG،PWD)؛ نهEDITOR، نه متغیرهای ساختهی~/.bashrc، نهaliasها. (PATHاینجا از فایل سیستمی خوانده میشود؛ روی بعضی سیستمها فقط/usr/bin:/binاست. هیچوقت فرض نکن ابزارت پیدا میشود: مسیر کامل بنویس.) SHELL=/bin/sh، نه bash.shدر Ubuntu همانdashاست: ساده و سریع، ولی ویژگیهای bash (مثل$RANDOM،[[ ]]، آرایه) را ندارد.RANDOMوBASH_VERSIONخالی ماندند.- پوشهی جاری خانهی کاربر است، پس مسیرهای نسبی به خانه اشاره میکنند، نه جایی که فکر میکنی.
اگر کارت به bash نیاز دارد، در اول crontab یک خط SHELL=/bin/bash بنویس (و PATH=... اگر لازم است). همین را آزمایش میکنم؛ در همین دقیقهی انتظار، یک فایل سیستمی هم میسازم (مثال ۶):
cat > /tmp/cron-b.tab <<'EOF'SHELL=/bin/bash* * * * * echo "SHELL=$SHELL RANDOM=[$RANDOM] BASH_VERSION=[$BASH_VERSION]" > /tmp/cron-shell2.txtEOFsu - alice -c 'crontab /tmp/cron-b.tab'cat > /etc/cron.d/lx-demo <<'EOF'* * * * * root echo "از /etc/cron.d در $(date +\%T)" >> /tmp/cron-d.logEOFecho "(صبر برای اجرای دقیقهی بعد...)"sleep 65echo "--- با SHELL=/bin/bash:"cat /tmp/cron-shell2.txt(صبر برای اجرای دقیقهی بعد...)--- با SHELL=/bin/bash:SHELL=/bin/bash RANDOM=[29899] BASH_VERSION=[5.2.21(1)-release]حالا RANDOM عدد دارد و BASH_VERSION مقدار. (فایل /etc/cron.d/lx-demo را در مثال ۶ میبینیم.)
مثال ۴: خروجی و لاگ کارها
Section titled “مثال ۴: خروجی و لاگ کارها”کار cron بدون ترمینال اجرا میشود. پس خروجیاش (stdout و stderr) کجا میرود؟ به ایمیل کاربر (اگر یک MTA نصب باشد)، و گرنه دور ریخته میشود. دو کار آزمایشی در مثال ۳: یکی خروجی را به فایل ریدایرکت کرده بود و یکی نه:
echo "--- کاری که >> فایل 2>&1 داشت: خطا در فایل لاگ ثبت شد:"cat /tmp/cron-err.log | tail -2echo "--- و کاری که ریدایرکت نداشت؛ cron در syslog چه میگوید؟"grep CRON /var/log/syslog | grep -E 'CMD \(ls /no/such/dir\)|No MTA' | tail -3 | sed -E 's/^[^ ]+ server //'--- کاری که >> فایل 2>&1 داشت: خطا در فایل لاگ ثبت شد:ls: cannot access '/no/such/dir': No such file or directory--- و کاری که ریدایرکت نداشت؛ cron در syslog چه میگوید؟CRON[16193]: (CRON) info (No MTA installed, discarding output)CRON[16960]: (alice) CMD (ls /no/such/dir)CRON[16955]: (CRON) info (No MTA installed, discarding output)No MTA installed, discarding output: چون روی این ماشین برنامهی ایمیل (MTA) نیست، خروجی کاری که ریدایرکت نداشت بیصدا دور ریخته شد. پس برای هر کار cron که اهمیت دارد، خروجی را صریحاً ذخیره کن: دستور >> /var/log/job.log 2>&1 (>> اضافه میکند؛ 2>&1 خطا را هم به همان فایل میبرد؛ درس ریدایرکت). و برای کارهایی که نمیخواهی ایمیل بیاید، بالای crontab MAILTO="" بگذار. دیدن اجراها:
| چه میخواهی | دستور |
|---|---|
| آیا cron کار را اجرا کرد؟ | grep CRON /var/log/syslog (روی Debian/Ubuntu) یا journalctl -u cron |
| فقط کارهای یک کاربر | grep 'CRON.*(alice)' /var/log/syslog |
| دستور کار چه خروجی داد؟ | فایل لاگی که خودت ریدایرکت کردهای |
| روی Red Hat | /var/log/cron |
مثال ۵: خطای بزرگ، علامت %
Section titled “مثال ۵: خطای بزرگ، علامت %”در crontab علامت % معنی ویژه دارد: به «خط جدید» تبدیل میشود و هر چه بعد از اولین % بیاید بهعنوان ورودی استاندارد (stdin) به دستور داده میشود. پس date +%F بدون escape خراب است. ببین در لاگ چه میشود (یک کار با escape و یک کار بدون آن):
cat > /tmp/cron-c.tab <<'EOF'* * * * * echo "escaped: $(date +\%F)" >> /tmp/cron-pct.log 2>&1* * * * * echo "unescaped: $(date +%F)" >> /tmp/cron-pct.log 2>&1EOFsu - alice -c 'crontab /tmp/cron-c.tab'echo "(صبر برای اجرای دقیقهی بعد...)"sleep 65echo "--- خروجی (فقط کار با escape نوشت: escaped، نه unescaped):"cat /tmp/cron-pct.logecho "--- cron دستور دوم را چطور دید؟ (بعد از % بریده شد):"grep CRON /var/log/syslog | grep 'CMD (echo "unescaped' | tail -1 | sed -E 's/^[^ ]+ server //'(صبر برای اجرای دقیقهی بعد...)--- خروجی (فقط کار با escape نوشت: escaped، نه unescaped):escaped: 2026-10-04--- cron دستور دوم را چطور دید؟ (بعد از % بریده شد):CRON[17008]: (alice) CMD (echo "unescaped: $(date +)کار بدون escape (unescaped) فقط تا قبل از % (echo "unescaped: $(date +) به shell رسید، پس یک دستور ناقص اجرا شد (گیومهی باز و پرانتز باز) و خطای سینتکس داد، آن هم بدون هیچ لاگی. راهحل: % را همیشه بهصورت \% بنویس، یا بهتر: دستورهای پیچیده را در یک اسکریپت بگذار (که در آن % معمولی است) و فقط اسکریپت را در crontab صدا بزن.
مثال ۶: cron سیستمی، /etc/crontab و /etc/cron.d
Section titled “مثال ۶: cron سیستمی، /etc/crontab و /etc/cron.d”علاوه بر crontab هر کاربر، خود سیستم هم کارهایی دارد. تفاوت: در فایلهای سیستمی بین زمان و دستور یک فیلد نام کاربر هست:
echo "--- /etc/crontab (بدون توضیح):"grep -v '^#' /etc/crontab | grep -v '^$'echo "--- /etc/cron.d:"ls /etc/cron.decho "--- /etc/cron.daily:"ls /etc/cron.dailyecho "--- لیست اسکریپتهایی که run-parts برای cron.daily اجرا میکند (--test):"run-parts --test /etc/cron.dailyecho "--- فایلی که خودم در /etc/cron.d ساختم دقیقهی قبل اجرا شد؟"cat /tmp/cron-d.log | tail -2--- /etc/crontab (بدون توضیح):SHELL=/bin/sh17 * * * * root cd / && run-parts --report /etc/cron.hourly25 6 * * * root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.daily; }47 6 * * 7 root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.weekly; }52 6 1 * * root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.monthly; }--- /etc/cron.d:e2scrub_alllx-demo--- /etc/cron.daily:apt-compatdpkglogrotateman-db--- لیست اسکریپتهایی که run-parts برای cron.daily اجرا میکند (--test):/etc/cron.daily/apt-compat/etc/cron.daily/dpkg/etc/cron.daily/logrotate/etc/cron.daily/man-db--- فایلی که خودم در /etc/cron.d ساختم دقیقهی قبل اجرا شد؟از /etc/cron.d در 08:00:01از /etc/cron.d در 08:01:01/etc/crontab: جدول اصلی سیستم؛ خطهایشدقیقه ساعت ... کاربر دستورهستند و باrun-partsهمهی اسکریپتهای پوشههایcron.hourly،cron.daily،cron.weeklyوcron.monthlyرا اجرا میکنند (روی این ماشینcron.dailyساعت ۶:۲۵ هر روز)./etc/cron.d/: هر برنامه میتواند فایل جدا بگذارد (با فیلد کاربر)؛ اضافهکردن فایل یک راه تمیز برای کار سیستمی است. فایل نمونهام هم اجرا شد./etc/cron.daily/و …: فقط یک اسکریپت اجرایی بگذار (بدون فیلد زمان)؛ زمانش را سیستم تعیین میکند. اسکریپتهایی مثلlogrotate(چرخش لاگها) وman-dbهمینجا هستند.
مثال ۷: بکاپ شبانه (تمرین اصلی)
Section titled “مثال ۷: بکاپ شبانه (تمرین اصلی)”حالا کار اصلی: هر شب ساعت ۲ بکاپ. دستور بکاپ را در یک اسکریپت میگذارم (بهجای یک خط طولانی با %): tar از درس آرشیو، نام با تاریخ، و پاککردن همهی بکاپها بهجز ۳ تای آخر. یک «سایت» نمونه هم میسازم:
#!/bin/bash# بکاپ شبانهی سایت: فشرده با تاریخ در نام؛ فقط ۳ نسخهی آخر نگه داشته میشودsrc=/srv/sitedest=/var/backups/sitemkdir -p "$dest"f="$dest/site-$(date +%F_%H%M).tar.gz"tar -czf "$f" -C "$(dirname "$src")" "$(basename "$src")" && echo "$(date '+%F %T') بکاپ ساخته شد: $f"ls -1t "$dest"/site-*.tar.gz | tail -n +4 | xargs -r rm -vchmod +x /usr/local/bin/lx-backup.shmkdir -p /srv/site && echo "<h1>سایت من</h1>" > /srv/site/index.htmlecho "--- اول با دست اجرا میکنم تا مطمئن شوم خود اسکریپت درست است:"/usr/local/bin/lx-backup.shls /var/backups/site--- اول با دست اجرا میکنم تا مطمئن شوم خود اسکریپت درست است:2026-10-04 08:01:30 بکاپ ساخته شد: /var/backups/site/site-2026-10-04_0801.tar.gzsite-2026-10-04_0801.tar.gzقاعدهی طلایی: قبل از دادن کار به cron، آن را با دست اجرا کن (بهتر: با همان محیط محدود، مثلاً env -i HOME=/root /bin/sh -c '/usr/local/bin/lx-backup.sh'). حالا خط crontab. زمان واقعیاش 0 2 * * * است؛ ولی نمیخواهم تا ساعت ۲ شب صبر کنم، پس همان خط را مینویسم و فقط بهجای 0 2 دقیقه و ساعتِ «دو دقیقهی دیگر» را میگذارم (با date -d '+2 minutes') تا دقیقاً همین مکانیزم را ببینی:
when=$(date -d '+2 minutes' '+%-M %-H')echo "زمانبندی دو دقیقهی بعد: «$when * * *» (در واقعیت: «0 2 * * *»)"printf '%s * * * /usr/local/bin/lx-backup.sh >> /var/log/lx-backup.log 2>&1\n' "$when" > /tmp/cron-bk.tabcrontab -u root /tmp/cron-bk.tabcrontab -lecho "(صبر تا اجرای کار...)"sleep 170echo "--- لاگ بکاپ (کار cron):"cat /var/log/lx-backup.logls -l /var/backups/site | tail -n +2 | awk '{print $5, $9}'زمانبندی دو دقیقهی بعد: «3 8 * * *» (در واقعیت: «0 2 * * *»)3 8 * * * /usr/local/bin/lx-backup.sh >> /var/log/lx-backup.log 2>&1(صبر تا اجرای کار...)--- لاگ بکاپ (کار cron):2026-10-04 08:03:01 بکاپ ساخته شد: /var/backups/site/site-2026-10-04_0803.tar.gz185 site-2026-10-04_0801.tar.gz185 site-2026-10-04_0803.tar.gzبکاپ در لحظهی مقرر خودکار ساخته شد، خروجیاش در /var/log/lx-backup.log ثبت شد و نسخههای اضافه (اگر بیشتر از ۳ میشد) پاک میشدند. برای ساعت ۲ شب فقط همان 0 2 را مینویسی. توجه: چون cron با ساعت سیستم کار میکند، به منطقهی زمانی (date) سرور دقت کن؛ ساعت ۲ شب به وقت UTC یا تهران؟
مثال ۸: جلوگیری از اجرای همزمان با flock
Section titled “مثال ۸: جلوگیری از اجرای همزمان با flock”اگر کاری که هر ۵ دقیقه اجرا میشود گاهی ۷ دقیقه طول بکشد، اجراها روی هم میافتند. با flock یک «قفل فایل» میگیری تا نسخهی دوم اگر اولی هنوز کار میکند، فوراً بیرون برود:
echo "--- یک کار طولانی (۵ ثانیه) در پسزمینه با قفل:"flock -n /tmp/cron-job.lock sh -c 'echo "نسخهی اول شروع شد"; sleep 5; echo "نسخهی اول تمام شد"' &sleep 1echo "--- نسخهی دوم در همان زمان (flock -n یعنی اگر قفل است منتظر نمان):"flock -n /tmp/cron-job.lock sh -c 'echo "نسخهی دوم اجرا شد"' || echo "قفل دست دیگری است؛ نسخهی دوم رد شد"wait--- یک کار طولانی (۵ ثانیه) در پسزمینه با قفل:نسخهی اول شروع شد--- نسخهی دوم در همان زمان (flock -n یعنی اگر قفل است منتظر نمان):قفل دست دیگری است؛ نسخهی دوم رد شدنسخهی اول تمام شددر crontab مینویسی: */5 * * * * flock -n /tmp/job.lock /usr/local/bin/job.sh. نسخهی دوم تا پایان اولی اجرا نمیشود.
مثال ۹: systemd timer، جایگزین امروزی
Section titled “مثال ۹: systemd timer، جایگزین امروزی”systemd timer همان کار را با دو فایل unit انجام میدهد: یک .service (چه کاری) و یک .timer (چه زمانی). مزایا نسبت به cron: لاگ در journal با journalctl -u، وابستگی به سایر سرویسها، گزینهی Persistent=true (اگر ماشین موقع زمان مقرر خاموش بود، بعد از روشنشدن اجرا میکند)، و تأخیر تصادفی. زمانها در systemd شکل دیگری دارند (OnCalendar=)؛ ابزار systemd-analyze calendar آنها را بررسی میکند. ببین «هر شب ساعت ۲» و همان اشتباه * 2 * * * را:
echo "--- هر شب ساعت 2 (معادل 0 2 * * *):"systemd-analyze calendar '*-*-* 02:00:00' --iterations=3 | grep -E 'Normalized|Next elapse|Iteration'echo "--- و «هر دقیقهی ساعت 2» (معادل * 2 * * *)، ۳ اجرای اول:"systemd-analyze calendar '*-*-* 02:*:00' --iterations=3 | grep -E 'Next elapse|Iteration'--- هر شب ساعت 2 (معادل 0 2 * * *):Normalized form: *-*-* 02:00:00 Next elapse: Mon 2026-10-05 02:00:00 UTC Iteration #2: Tue 2026-10-06 02:00:00 UTC Iteration #3: Wed 2026-10-07 02:00:00 UTC--- و «هر دقیقهی ساعت 2» (معادل * 2 * * *)، ۳ اجرای اول: Next elapse: Mon 2026-10-05 02:00:00 UTC Iteration #2: Mon 2026-10-05 02:01:00 UTC Iteration #3: Mon 2026-10-05 02:02:00 UTCسه اجرای اول برای ساعت 02:00:00 فقط هر شب یک بار است (سه شب پشتسرهم)، ولی برای 02:*:00 سه اجرای اول 02:00، 02:01، 02:02اند: همان اشتباه * 2 * * *. یک timer سادهی واقعی: هر ۲۰ ثانیه یک بار («tick») بعد از ۵ ثانیهی اول:
cat > /etc/systemd/system/lx-tick.service <<'EOF'[Unit]Description=Demo job run by a timer
[Service]Type=oneshotExecStart=/bin/sh -c 'echo tick at $(date +%%T)'EOFcat > /etc/systemd/system/lx-tick.timer <<'EOF'[Unit]Description=Run lx-tick every 20 seconds
[Timer]OnActiveSec=5OnUnitActiveSec=20AccuracySec=1s
[Install]WantedBy=timers.targetEOFsystemctl daemon-reloadsystemctl enable --now lx-tick.timersleep 50echo "--- زمانبندها (systemctl list-timers):"systemctl list-timers lx-tick.timer --no-pager | cut -c1-100echo "--- لاگ اجراها (journalctl -u):"journalctl -u lx-tick.service --since '2 minutes ago' --no-pager -o cat | grep '^tick at'systemctl disable --now lx-tick.timerCreated symlink /etc/systemd/system/timers.target.wants/lx-tick.timer → /etc/systemd/system/lx-tick.timer.--- زمانبندها (systemctl list-timers):NEXT LEFT LAST PASSED UNIT ACTIVATESSun 2026-10-04 08:05:32 UTC 17s Sun 2026-10-04 08:05:12 UTC 2s ago lx-tick.timer lx-tick.service
1 timers listed.Pass --all to see loaded but inactive timers, too.--- لاگ اجراها (journalctl -u):tick at 08:04:30tick at 08:04:51tick at 08:05:12Removed "/etc/systemd/system/timers.target.wants/lx-tick.timer".%%T در unit file همان %T است (در فایلهای systemd هم % ویژه است و باید دو بار نوشته شود). list-timers ستونهای NEXT (اجرای بعدی)، LEFT (چقدر مانده)، LAST و PASSED (آخرین اجرا) دارد؛ و لاگ هر اجرا در journal است. برای بکاپ شبانه مینویسی OnCalendar=*-*-* 02:00:00 و Persistent=true. این درس cron را اصلی گرفت چون همهجا هست و ساده؛ systemd timer برای کارهای جدیتر یا سرویسمحور بهتر است.
پشت پرده: cron چطور کار میکند؟
Section titled “پشت پرده: cron چطور کار میکند؟”cron یک daemon است (سرویس systemd): هر دقیقه (در لحظهی :۰۰) بیدار میشود، فهرست crontab ها را (اگر تغییری کرده باشند، دوباره) میخواند و برای هر خطی که با ساعت و تاریخ الآن میخورد، یک پروسهی فرزند میسازد که دستور را با SHELL (پیشفرض /bin/sh -c) اجرا میکند. ببین:
systemctl is-active cronps -o pid,comm,args -C cron | cut -c1-80echo "--- crontab های کاربرها اینجا نگهداری میشوند (فقط root میخواند):"ls -l /var/spool/cron/crontabs/active PID COMMAND COMMAND 81 cron /usr/sbin/cron -f -P--- crontab های کاربرها اینجا نگهداری میشوند (فقط root میخواند):total 8-rw------- 1 alice crontab 320 Oct 4 08:00 alice-rw------- 1 root crontab 259 Oct 4 08:01 rootcrontab هر کاربر فایلی در /var/spool/cron/crontabs/ است (برای همین crontab -e از فایل موقت و بررسی نحو استفاده میکند و مستقیم ویرایشش نمیکنی). cron هر دقیقه mtime این فایلها را نگاه میکند. دو پیامد مهم: cron کارهای ازدسترفته را جبران نمیکند (اگر سرور ساعت ۲ خاموش بود، آن اجرا نیست)، و کمترین واحد زمانی یک دقیقه است (برای ثانیهای باید از systemd timer یا حلقهی sleep استفاده کنی).
جدولهای مرجع
Section titled “جدولهای مرجع”| دستور | کار |
|---|---|
crontab -e |
ویرایش crontab خودت |
crontab -l |
فهرست crontab |
crontab -r |
حذف همهی crontab (بدون تأیید!) |
crontab فایل |
جایگزین کردن کل جدول با یک فایل |
crontab -u کاربر -l |
crontab کاربر دیگر (root) |
grep CRON /var/log/syslog |
تاریخچهی اجراها (Debian/Ubuntu) |
journalctl -u cron |
همان از journal |
flock -n /tmp/x.lock دستور |
جلوگیری از اجرای همزمان |
systemd-analyze calendar 'عبارت' |
آزمایش زمانبندی systemd |
systemctl list-timers |
فهرست timer ها |
| نکتهی نوشتن crontab | چرا |
|---|---|
% را به \% بنویس |
% در cron یعنی خط جدید |
| مسیر کامل دستورها | PATH محدود است |
SHELL=/bin/bash در بالای فایل |
اگر bashism داری |
>> /var/log/job.log 2>&1 |
وگرنه خروجی دور ریخته/ایمیل میشود |
| دستور پیچیده را در یک اسکریپت بگذار | آزمایش و دیباگ سادهتر |
خط آخر باید Enter داشته باشد |
بعضی نسخهها آخرین خط بیخطجدید را نادیده میگیرند |
اشتباهات رایج
Section titled “اشتباهات رایج”۱) % بدون \
Section titled “۱) % بدون \”مثال ۵. راهحل: \% یا اسکریپت.
۲) * 2 * * * بهجای 0 2 * * *
Section titled “۲) * 2 * * * بهجای 0 2 * * *”مثال ۱ و ۹: ۶۰ بار در ساعت ۲ اجرا میشود. راهحل: دقیقه را بنویس.
۳) فرض اینکه «در ترمینال کار میکند، پس در cron هم»
Section titled “۳) فرض اینکه «در ترمینال کار میکند، پس در cron هم»”مثال ۳: محیط کوچک، /bin/sh، پوشهی جاری خانه. راهحل: مسیرهای کامل، SHELL=/PATH= در crontab، و آزمایش با env -i:
echo "--- آزمایش یک اسکریپت با محیط خالی (شبیه cron):"env -i HOME=/root /bin/sh -c '/usr/local/bin/lx-backup.sh | head -1'ls /var/backups/site | wc -l--- آزمایش یک اسکریپت با محیط خالی (شبیه cron):2026-10-04 08:05:15 بکاپ ساخته شد: /var/backups/site/site-2026-10-04_0805.tar.gz3۴) خروجی نامرئی
Section titled “۴) خروجی نامرئی”مثال ۴: بدون ریدایرکت خروجی (و خطا) دور ریخته میشود. راهحل: >> لاگ 2>&1 برای هر کار مهم، و grep CRON /var/log/syslog.
۵) crontab -r بهجای crontab -e
Section titled “۵) crontab -r بهجای crontab -e”e و r کنار هماند و -r بیپرسش همهی جدول را پاک میکند:
useradd -m -s /bin/bash bobsu - bob -c 'echo "0 3 * * * /bin/true" | crontab -; crontab -l; echo "--- crontab -r (یک اشتباه تایپی):"; crontab -r; crontab -l'userdel -r bob 2>/dev/null0 3 * * * /bin/true--- crontab -r (یک اشتباه تایپی):no crontab for bobهیچ تأییدی نخواست و جدول رفت. راهحل: از crontab مهم یک کپی نگه دار (crontab -l > ~/crontab.bak)، و اگر میخواهی -i را (crontab -ri) برای پرسیدن تأیید بهکار ببر.
۶) اجرای همپوشان و کارهای ازدسترفته
Section titled “۶) اجرای همپوشان و کارهای ازدسترفته”مثال ۸ (flock) و توضیح «پشت پرده»: cron جبران نمیکند. برای کارهایی که از دست رفتنشان مهم است از systemd timer با Persistent=true یا anacron استفاده کن.
فایل /etc/crontab را بخوان و بگو اسکریپتهای cron.daily چه ساعتی اجرا میشوند.
دیدن جواب
grep 'cron.daily' /etc/crontab25 6 * * * root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.daily; }فیلدهای اول 25 6 * * * هستند: دقیقه ۲۵، ساعت ۶، هر روز؛ یعنی هر روز ساعت ۶:۲۵ صبح (به وقت سرور).
تمرین اصلی: هر شب ساعت ۲ یک بکاپ خودکار بگیر. خط واقعی crontab برای اسکریپت بکاپ مثال ۷ چیست؟ آن را بنویس، با crontab -l ببین، و صحتش را با یک فایل آزمایشی بررسی کن. (برای ساعت ۲ شب نمیشود صبر کرد؛ مثال ۷ همین را با زمان دو دقیقه بعد آزمایش کرد.)
دیدن جواب
echo '0 2 * * * /usr/local/bin/lx-backup.sh >> /var/log/lx-backup.log 2>&1' > /tmp/cron-real.tabcrontab -u root /tmp/cron-real.tabcrontab -lecho "--- اگر این خط روی یک ماشین اجرا شود، اجراهای بعدی ساعت 02:00 هستند (با systemd-analyze همان زمانبندی را بررسی میکنم):"systemd-analyze calendar '*-*-* 02:00:00' --iterations=2 | grep -E 'Next elapse|Iteration'crontab -r0 2 * * * /usr/local/bin/lx-backup.sh >> /var/log/lx-backup.log 2>&1--- اگر این خط روی یک ماشین اجرا شود، اجراهای بعدی ساعت 02:00 هستند (با systemd-analyze همان زمانبندی را بررسی میکنم): Next elapse: Mon 2026-10-05 02:00:00 UTC Iteration #2: Tue 2026-10-06 02:00:00 UTCهمان بکاپ را با systemd timer بنویس: lx-backup.service (که اسکریپت را اجرا میکند) و lx-backup.timer با OnCalendar=*-*-* 02:00:00 و Persistent=true. تایمر را فعال کن، با systemctl list-timers ببین، و سرویس را یک بار دستی (systemctl start) اجرا کن تا مطمئن شوی کار میکند.
دیدن جواب
cat > /etc/systemd/system/lx-backup.service <<'EOF'[Unit]Description=Nightly site backup
[Service]Type=oneshotExecStart=/usr/local/bin/lx-backup.shEOFcat > /etc/systemd/system/lx-backup.timer <<'EOF'[Unit]Description=Run the site backup every night at 02:00
[Timer]OnCalendar=*-*-* 02:00:00Persistent=true
[Install]WantedBy=timers.targetEOFsystemctl daemon-reloadsystemctl enable --now lx-backup.timersystemctl list-timers lx-backup.timer --no-pager | cut -c1-110echo "--- آزمایش دستی سرویس:"systemctl start lx-backup.servicejournalctl -u lx-backup.service --since '2 minutes ago' --no-pager -o cat | grep -E 'بکاپ ساخته شد|Finished'systemctl disable --now lx-backup.timerCreated symlink /etc/systemd/system/timers.target.wants/lx-backup.timer → /etc/systemd/system/lx-backup.timer.NEXT LEFT LAST PASSED UNIT ACTIVATESMon 2026-10-05 02:00:00 UTC 17h - - lx-backup.timer lx-backup.service
1 timers listed.Pass --all to see loaded but inactive timers, too.--- آزمایش دستی سرویس:2026-10-04 08:05:15 بکاپ ساخته شد: /var/backups/site/site-2026-10-04_0805.tar.gzFinished lx-backup.service - Nightly site backup.Removed "/etc/systemd/system/timers.target.wants/lx-backup.timer".آزمونک
Section titled “آزمونک”پنج فیلد زمان cron به ترتیب چه هستند؟
مثلاً 30 8 * * 1-5 یعنی ۸:۳۰ صبح دوشنبه تا جمعه.
تفاوت «0 2 * * *» و «* 2 * * *»؟
فیلد دقیقه * یعنی هر دقیقه؛ برای یک بار باید 0 بنویسی.
چرا date +%F در یک خط crontab مشکل ایجاد میکند و چطور درست میشود؟
بدون escape دستور بعد از % بریده میشود و کار بیصدا شکست میخورد.
کار cron در ترمینال تو کار میکند ولی در cron نه. محتملترین علت؟
هیچوقت فرض نکن محیط تو را دارد.
کجا میبینی cron یک کار را اجرا کرد؟
و خروجی دستور را اگر ریدایرکت کردهای در فایل لاگ خودت.
مزیت اصلی systemd timer با Persistent=true نسبت به cron؟
cron کارهای از دست رفته را جبران نمیکند؛ لاگ journal و وابستگی هم مزیتهای دیگرند.
جمعبندی
Section titled “جمعبندی”- cron هر دقیقه crontab ها را میخواند و کارها را با
/bin/shاجرا میکند. هر خط:دقیقه ساعت روز-ماه ماه روز-هفته دستور؛*هر مقدار،a,bفهرست،a-bبازه،*/nقدم.@reboot،@dailyو… میانبر. crontab -eویرایش،crontab -lفهرست؛crontab -rهمه را پاک میکند (بیپرسش!). جدول سیستمی:/etc/crontabو/etc/cron.d(با فیلد کاربر)، و اسکریپتهای/etc/cron.dailyو…- محیط کارها کوچک است (
SHELL=/bin/sh، پوشهی جاری خانه، بدون.bashrc)؛ مسیر کامل بنویس و در صورت نیازSHELL=وPATH=بالای crontab بگذار. %را\%بنویس؛ دستورهای پیچیده را در اسکریپت بگذار.* 2 * * *≠0 2 * * *.- خروجی را صریحاً ذخیره کن (
>> لاگ 2>&1)؛ اجراها را باgrep CRON /var/log/syslogببین؛ همپوشانی را باflock -nبگیر. - cron کارهای ازدسترفته را جبران نمیکند؛ systemd timer (
OnCalendar=،Persistent=true، لاگ در journal) جایگزین امروزی است.
| دستور | کاری که میکند |
|---|---|
crontab -e / crontab -l | ویرایش / فهرست crontab خودت |
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1 | هر شب ساعت ۲ با لاگ |
*/5 * * * * دستور | هر ۵ دقیقه |
0 9 * * 1-5 دستور | ۹ صبح، دوشنبه تا جمعه |
@reboot دستور | بعد از هر بوت |
date +\%F (داخل crontab) | escape علامت % |
SHELL=/bin/bash (خط اول crontab) | استفاده از bash |
grep CRON /var/log/syslog | تاریخچهی اجراها |
*/5 * * * * flock -n /tmp/job.lock job.sh | بدون اجرای همپوشان |
systemd-analyze calendar "*-*-* 02:00:00" | آزمایش زمانبندی systemd |
systemctl list-timers | timer های فعال |