SSD Nodes Learn 🎉 VPS از $5.50/ماه
راهنماها Matt Connorتوسط Matt Connor

چرا کرون‌جاب (cron job) اجرا نمی‌شود؟ 5 دلیل اصلی

اگر کرون‌جاب شما اجرا نمی‌شود، این 5 مورد را بررسی کنید: محدودیت PATH، عدم اسکیپ علامت %، فایل crontab اشتباه، گم شدن خروجی در ایمیل و فرض اجرای اسکریپت در شل کاربر.

چرا کرون‌جاب شما اجرا نمی‌شود

یک کرون‌جاب (cron job) که «هرگز اجرا نمی‌شود»، تقریباً همیشه اجرا شده است. این کار در محیطی غیر از شل (shell) شما اجرا شده، در همان ثانیه اول شکست خورده و پیام خطای آن به جایی ارسال شده که شما آن را نمی‌خوانید. پنج دلیل، تقریباً تمام گزارش‌های خرابی را توضیح می‌دهند: مسیر جستجو (search path)، علامت درصد، فایل crontab اشتباه، خروجی که به ایمیل ارسال شده و اسکریپتی که انتظار یک نشست ورود (login session) را دارد.

کرون یک دیمون (سرویس پس‌زمینه) است که فایل‌های crontab را می‌خواند و دستورات را طبق زمان‌بندی شروع می‌کند. این سرویس .bashrc شما را نمی‌خواند، ترمینال باز نمی‌کند، شل ورود (login shell) را اجرا نمی‌کند و در صورت شکست یک دستور، به شما اطلاع نمی‌دهد. تمام دلایل زیر از این چهار واقعیت ناشی می‌شوند.

آن‌ها را به ترتیب بررسی کنید و با پرسشی که زیربنای همه آن‌هاست شروع کنید: آیا کرون اصلاً دستور را اجرا کرد؟ «کرون هرگز جاب را شروع نکرد» و «جاب شروع شد و از کار افتاد» دو مشکل متفاوت هستند که هیچ وجه اشتراکی ندارند، بنابراین ابتدا به این پرسش پاسخ دهید.

آیا cron اصلاً اجرا شده است؟

نام unit این daemon در خانواده‌های مختلف توزیع‌های لینوکسی متفاوت است. هر دو را بررسی کنید و سپس لاگ را بخوانید.

systemctl status cron
systemctl status crond
journalctl -u cron --since "2 hours ago"
journalctl -u crond --since "2 hours ago"

در Debian و Ubuntu نام این unit برابر با cron است. در Fedora، Rocky و Alma این نام crond است. در هر سیستم فقط یکی از این نام‌ها وجود دارد، بنابراین اگر یکی از دستورات خطای unknown unit برگرداند، طبیعی است و نشان‌دهنده نقص نیست.

ورودی‌هایی را که سیستم خودتان ثبت کرده است، بخوانید. به دنبال خطی که از یک راهنما کپی شده است نگردید، زیرا عبارت‌بندی لاگ‌ها بین پیاده‌سازی‌های مختلف cron و تنظیمات لاگ‌گیری متفاوت است. شما فقط دو مورد را بررسی می‌کنید: آیا در دقیقه‌ای که زمان‌بندی کرده‌اید ورودی وجود دارد، و آیا آن ورودی نام دستور شما را ذکر کرده است یا خیر. وجود ورودی با نام دستور شما به این معنی است که cron وظیفه خود را انجام داده و خطا در داخل خودِ دستور است. نبود هیچ ورودی به این معنی است که cron اصلاً زمان‌بندی شما را دریافت نکرده است که این مورد، علت شماره 3 در پایین است.

برخی از imageها پیام‌های cron را به‌جای journal، از طریق rsyslog به یک فایل می‌فرستند. در مسیر /var/log به دنبال فایلی با نام cron یا syslog بگردید و انتهای آن را مطالعه کنید.

ls -l /var/log
sudo tail -n 50 /var/log/syslog

اگر نه unit و نه لاگ وجود دارد، ممکن است cron اصلاً نصب نشده باشد. imageهای حداقلی (minimal) ابری و کانتینرها اغلب آن را شامل نمی‌شوند.

dpkg -l cron
rpm -q cronie
sudo apt install cron
sudo dnf install cronie
sudo systemctl enable --now cron

دلیل 1: cron متغیر PATH شما را ندارد

شل تعاملی شما متغیر PATH را از فایل‌های /etc/profile، ~/.profile، ~/.bashrc و هر فایلی که این‌ها فراخوانی می‌کنند، می‌سازد. هیچ‌کدام از این‌ها برای یک job در cron اجرا نمی‌شوند. cron دستور را با محیط بسیار محدود خود شروع می‌کند، بنابراین برنامه‌ای که خارج از دایرکتوری‌های استاندارد سیستم قرار دارد، پیدا نمی‌شود. هر چیزی در مسیرهای /usr/local/bin، /opt، یک مدیریت‌کننده نسخه زبان (version manager)، یک محیط مجازی پایتون (Python virtual environment) یا یک فضای کاری Go، مشمول این مشکل است. job در همان خط اول شکست می‌خورد و شل یک خطای "not found" تولید می‌کند که متن دقیق آن به شلی که آن را اجرا کرده بستگی دارد.

مسیر واقعی (absolute path) هر دستوری که job شما استفاده می‌کند را پیدا کنید.

command -v docker
command -v node
readlink -f "$(command -v node)"

سپس یا آن مسیرهای کامل را مستقیماً در job بنویسید، یا متغیر PATH را یک‌بار در ابتدای crontab مقداردهی کنید.

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 3 * * * /usr/local/bin/mytool run

آن لیست را از ماشین خودتان با دستور echo "$PATH" استخراج کنید و هر چیزی که فقط در یک نشست تعاملی (interactive session) وجود دارد را حذف کنید. یک قانون مهم در اینجا وجود دارد: cron متغیرها را در این خطوط انتساب (assignment lines) بسط نمی‌دهد. PATH=$PATH:/usr/local/bin متن دقیق $PATH:/usr/local/bin را ذخیره می‌کند، بنابراین job در نهایت با یک مسیر جستجو مواجه می‌شود که هیچ دایرکتوری قابل‌استفاده‌ای در آن نیست. کل لیست را به‌صورت کامل بنویسید.

یک مدیریت‌کننده نسخه (version manager) به چیزی بیش از یک مسیر نیاز دارد. nvm، pyenv، rbenv و asdf یک تابع شل یا یک دایرکتوری shims از فایل .bashrc شما نصب می‌کنند و یک job در cron هرگز آن فایل را نمی‌خواند. باینری نسخه‌دار را با مسیر کامل فراخوانی کنید، یا اسکریپت init مربوط به آن مدیریت‌کننده را به عنوان خط اول اسکریپت خود source کنید.

دلیل 2: علامت درصد دستور شما را خاتمه می‌دهد

در فیلد دستور crontab، علامت % یک کاراکتر معمولی نیست. اولین % که با backslash فرار داده نشده باشد، دستور را خاتمه می‌دهد. هر چیزی که پس از آن بیاید به عنوان ورودی استاندارد (standard input) به دستور تحویل داده می‌شود و هر % بعدی به یک خط جدید تبدیل می‌گردد. این یک قابلیت واقعی در cron برای ارسال ورودی‌های کوتاه به یک برنامه است و به همین دلیل است که نام‌گذاری فایل با تاریخ در crontab، یک خطای کلاسیک محسوب می‌شود.

اگر 0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +%F).tar.gz /srv/site را بنویسید، دستور tar هرگز تاریخ فرمت‌شده را دریافت نمی‌کند. cron خط را در اولین % قطع می‌کند، بنابراین shell یک جایگزینی دستور (command substitution) ناقص دریافت می‌کند و باقی خط شما به عنوان ورودی استاندارد ارسال می‌شود. هر علامت درصد را با یک backslash فرار دهید.

0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +\%F).tar.gz /srv/site

دو لایه این یک خط را به ترتیب می‌خوانند. \% یک قانون cron است که توسط cron پیش از شروع هر کاری اعمال می‌شود. $(date +\%F) یک جایگزینی دستور است که بعداً توسط shellای که cron اجرا می‌کند، اعمال می‌شود. دانستن اینکه کدام لایه مالک کدام کاراکتر است، تمام فوت‌وفن کار است.

عادت ایمن‌تر این است که منطق برنامه را به‌طور کامل از crontab خارج کنید. آن را در یک اسکریپت قرار دهید، جایی که علامت درصد هیچ معنای خاصی ندارد.

#!/bin/bash
set -euo pipefail
stamp="$(date +%F)"
tar -czf "/srv/backups/site-${stamp}.tar.gz" /srv/site

در این صورت خط crontab فقط شامل یک مسیر و یک redirect خواهد بود و چیز دیگری در آن نیست. crontabای که بتوانید در یک نگاه بخوانید، crontabای است که می‌توانید آن را عیب‌یابی کنید.

Cause 3: which crontab did you edit?

There is no single crontab. There are several files, with different owners and different field counts, and a job written into the wrong one is invisible.

  • crontab -e edits the crontab of the user who runs the command. sudo crontab -e edits root's. Two people debugging the same box often end up reading two different files.
  • sudo crontab -l -u deploy lists another user's crontab, which is how you confirm what is actually installed for the account that should run the job.
  • /etc/crontab and every file in /etc/cron.d carry one extra field between the schedule and the command: the user to run as. Paste a five-field user crontab line into /etc/cron.d and the first word of your command is read as a username.
  • Files in /etc/cron.d must be named with letters, digits, underscores and hyphens. A file called backup.sh or site.conf is skipped because of its name alone. Rename it to backup and check your log again.
  • Files in /etc/cron.d should be owned by root, and must not be writable by group or others. ls -l /etc/cron.d shows you both facts at once.
  • Scripts dropped into /etc/cron.daily and its siblings follow the same naming rule and must also carry the execute bit. A missing execute bit is a silent skip.
  • /etc/cron.allow and /etc/cron.deny decide who may install a crontab at all. If either exists on your box, read it before assuming your user is allowed one.

Install a user crontab with the crontab command instead of editing the spool file by hand, because crontab parses the file before installing it. When you save, read what the command prints back to you. If it refuses the file, the previous version stays live and your change never took effect, which looks exactly like cron ignoring you.

The owner also decides permissions. A job in root's crontab creates root-owned files that the application reading them may not be able to write. A job in a normal user's crontab cannot read a root-only directory. Match the owner to the work: application maintenance belongs to the application's own account, which is the reasoning behind replacing WordPress wp-cron with a system cron job. The mode of the files your job creates comes from the umask it inherits, and that is another value which is not your shell's, so how umask sets file permissions is worth reading if a job's output lands unreadable.

دلیل 4: خروجی به ایمیلی ارسال شده که کسی آن را نمی‌خواند

cron تمام خروجی‌های استاندارد (standard output) و خطاهای استاندارد (standard error) یک job را جمع‌آوری می‌کند. اگر job حتی یک خط متن تولید کند، cron آن را به سیستم ایمیل محلی تحویل می‌دهد که برای مالک crontab یا هر مقصدی که در MAILTO تعیین شده، ارسال می‌شود. در یک VPS مینیمال، معمولاً هیچ MTA (عامل انتقال ایمیل) نصب نیست، بنابراین ایمیل به جایی نمی‌رسد. خطای شما برای لحظه‌ای وجود داشته و سپس ناپدید شده است. دقیقاً به همین دلیل است که یک job خراب، بی‌صدا به نظر می‌رسد.

خروجی را به فایلی که به آن دسترسی دارید هدایت کنید.

0 3 * * * /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1

>> خروجی استاندارد را به انتهای فایل اضافه می‌کند. 2>&1 خطای استاندارد را به همان جایی هدایت می‌کند که خروجی استاندارد در حال حاضر به آن اشاره دارد، بنابراین باید حتماً بعد از redirect اصلی قرار بگیرد. اگر به صورت معکوس یعنی 2>&1 >> file نوشته شود، خطای استاندارد در مقصد اصلی خود باقی می‌ماند و خطایی که به دنبال آن هستید، دقیقاً همان بخشی است که هرگز به فایل نمی‌رسد.

ژورنال سیستم (journal) مقصد مناسب دیگری است. logger خروجی را با برچسبی که شما انتخاب می‌کنید، در syslog می‌نویسد.

0 3 * * * /usr/local/sbin/backup-site.sh 2>&1 | logger -t backup-site

آن را با journalctl -t backup-site بخوانید. این کار باعث می‌شود خروجی job در کنار ورودی‌های cron باقی بماند و دنبال کردن خط زمانی آسان شود. اگر به ثبت سوابق اینکه چه کسی چه دستوری را روی سرور اجرا کرده نیاز دارید، این یک سیستم جداگانه است و حسابرسی دستورات کاربران روی سرور آن را پوشش می‌دهد.

قرار دادن MAILTO="" در ابتدای crontab، ارسال ایمیل برای jobهای زیر آن را غیرفعال می‌کند. تنظیم MAILTO روی یک آدرس واقعی فقط در صورتی مفید است که یک MTA فعال داشته باشید؛ بنابراین قبل از تکیه بر آن، مطمئن شوید که ایمیل واقعاً از سرور خارج می‌شود.

یک قانون هنگام عیب‌یابی: هرگز > /dev/null 2>&1 را به انتهای دستور اضافه نکنید. این محبوب‌ترین خط در هر crontab است، اما تنها مدرکی که دارید را از بین می‌برد. پس از اینکه job به درستی کار کرد، می‌توانید آن را دوباره اضافه کنید.

دلیل 5: اسکریپت به محیطی متکی است که cron آن را فراهم نمی‌کند

هنگامی که دستور پیدا شد و خروجی آن ثبت گردید، آنچه باقی می‌ماند تمام مواردی است که نشست (session) شما به‌طور خودکار در اختیارتان قرار می‌دهد.

  • ممکن است shell مورد استفاده bash نباشد. با استفاده از ls -l /bin/sh آن را بررسی کنید. در Debian و Ubuntu این دستور به dash اشاره دارد، بنابراین تست‌های double bracket، آرایه‌ها و source با خطای نحوی (syntax error) مواجه می‌شوند. یک خط #!/bin/bash به ابتدای اسکریپت اضافه کنید و اسکریپت را فراخوانی کنید، یا SHELL را در ابتدای crontab تنظیم نمایید.
  • دایرکتوری کاری (working directory) همان دایرکتوری نیست که شما در آن حضور داشتید. در همه جا از مسیرهای مطلق (absolute paths) استفاده کنید، یا در خط اول اسکریپت، دستور cd را برای رفتن به دایرکتوری مورد نظر به کار ببرید. مسیر نسبی، رایج‌ترین دلیل برای این است که یک job «وقتی دستی اجرا می‌شود کار می‌کند».
  • locale (تنظیمات منطقه‌ای) همان تنظیمات نشست شما نیست. هر چیزی که تاریخ یا عدد را فرمت می‌کند یا متن را مرتب می‌سازد، می‌تواند تحت یک LANG متفاوت، خروجی متفاوتی تولید کند. اگر مرحله‌ای در ادامه، آن خروجی را پردازش می‌کند، به جای حدس و گمان، locale را در خود اسکریپت تنظیم کنید.
  • هیچ TTY (ترمینال) وجود ندارد. دستوری که درخواست تأیید می‌کند، ویرایشگری را باز می‌کند یا نوار پیشرفت نمایش می‌دهد، ممکن است متوقف (hang) شود یا از کار بیفتد. هر پرچم (flag) غیرتعاملی که ابزار ارائه می‌دهد را اضافه کنید.
  • هیچ SSH agent وجود ندارد. SSH_AUTH_SOCK در محیط cron نیست، بنابراین دستور ssh یا rsync که به دلیل بارگذاری agent شما کار می‌کرد، اکنون در احراز هویت شکست می‌خورد. برای job یک کلید اختصاصی در نظر بگیرید که مالک آن، کاربرِ اجرای job باشد.
  • هیچ user session bus وجود ندارد، بنابراین systemctl --user از داخل یک cron job شکست می‌خورد مگر اینکه XDG_RUNTIME_DIR تنظیم شده باشد. استفاده از یک system unit راهکار بهتری است.

در Fedora، Rocky و Alma یک مورد مشکوک دیگر نیز وجود دارد. SELinux، اجرای cron jobها را محدود می‌کند؛ بنابراین اگر job بخواهد به مسیری با برچسب (label) غیرمنتظره دسترسی پیدا کند، حتی با وجود درست بودن مجوزهای فایل، دسترسی رد می‌شود. با استفاده از sudo ausearch -m avc -ts recent موارد رد شده (denials) را بررسی کنید و پیش از غیرفعال کردن هر چیزی، مبانی SELinux برای سرور را مطالعه کنید.

بررسی یک‌دقیقه‌ای برای مشاهده محیط اجرای cron

به‌جای حدس زدن محتویات محیط اجرای cron، آن را بخوانید. اسکریپتی بنویسید که تمام متغیرها را تخلیه کند، آن را برای اجرا در هر دقیقه زمان‌بندی کنید، منتظر بمانید و سپس فایل خروجی را بخوانید.

cat > /home/deploy/cron-probe.sh <<'EOF'
#!/bin/bash
echo "=== probe ==="
date -Is
pwd
id
echo "SHELL=$SHELL"
echo "LANG=$LANG"
command -v node || echo "node is not on this PATH"
env | sort
EOF
chmod +x /home/deploy/cron-probe.sh

یک خط به crontab کاربری که قرار است job اصلی را اجرا کند اضافه کنید؛ از مسیرهای مطلق (absolute paths) در هر دو سمت استفاده کنید.

* * * * * /home/deploy/cron-probe.sh >> /home/deploy/cron-probe.log 2>&1

یک دقیقه صبر کنید، سپس /home/deploy/cron-probe.log را بخوانید و آن را با خروجی همان دستورات در shell خود مقایسه کنید. خط PATH، دایرکتوری کاری و تنظیمات locale معمولاً به‌تنهایی علت شکست اجرای job را توضیح می‌دهند. به دو نکته در این تنظیمات توجه کنید: علامت‌های درصد داخل اسکریپت قرار دارند، جایی که قوانین cron اعمال نمی‌شود، و مسیر لاگ باید برای کاربری که job را اجرا می‌کند قابل نوشتن باشد.

به محض اینکه پاسخ خود را دریافت کردید، آن خط را از crontab حذف کنید. jobای که هر دقیقه اجرا می‌شود و داده‌ها را به یک فایل اضافه می‌کند، دیسک‌های کوچک را به‌سرعت و در سکوت پر خواهد کرد.

آیا زمان‌بندی همان چیزی است که مد نظر داشتید؟

یک خط crontab کاربر با پنج فیلد شروع می‌شود: دقیقه، ساعت، روز ماه، ماه، و روز هفته. دو مورد از این فیلدها به شکلی با هم تعامل دارند که باعث غافلگیری کاربران می‌شود.

هنگامی که هم روز ماه و هم روز هفته محدود شده باشند، یعنی هیچ‌کدام * نباشند، cron کار را زمانی اجرا می‌کند که یکی از این دو فیلد مطابقت داشته باشد. 0 0 13 * 5 به معنای «جمعه سیزدهم» نیست. این دستور در نیمه‌شب روز سیزدهم هر ماه و همچنین در نیمه‌شب هر جمعه اجرا می‌شود. برای رسیدن به یک روز خاص، یکی از دو فیلد را * بگذارید و شرط دیگر را داخل اسکریپت تست کنید.

سرویس cron از منطقه زمانی سیستم استفاده می‌کند. بسیاری از ایمیج‌های VPS به‌صورت پیش‌فرض روی UTC (زمان هماهنگ جهانی) تنظیم شده‌اند؛ بنابراین کاری که برای ساعت 03:00 زمان‌بندی کرده‌اید، در ساعت 03:00 UTC اجرا می‌شود که ممکن است معادل بعدازظهر شما باشد. دستور timedatectl نشان می‌دهد که سرور شما واقعاً از چه زمانی استفاده می‌کند. به جای فرض کردن اینکه زمان سرور با لپ‌تاپ شما یکی است، خروجی آن را بررسی کنید.

دو تله دیگر در زمان‌بندی وجود دارد که دانستن آن‌ها مفید است. @reboot زمانی اجرا می‌شود که خودِ cron شروع به کار می‌کند؛ این لحظه با زمانی که شبکه آماده می‌شود یکی نیست، بنابراین کاری که به DNS یا یک میزبان راه دور نیاز دارد، ممکن است هنگام بوت شکست بخورد اما در هر اجرای دستیِ بعدی موفق باشد. همچنین هیچ مکانیزمی مانع از شروع مجدد یک کار کُند در حالی که نسخه قبلی آن هنوز در حال اجراست، نمی‌شود. آن را با یک lock محدود کنید.

*/5 * * * * /usr/bin/flock -n /tmp/backup-site.lock /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1

دستور flock -n اگر lock از قبل گرفته شده باشد، بلافاصله متوقف می‌شود؛ بنابراین اجرای همپوشانی متوقف شده و کارها روی هم انباشته نمی‌شوند.

چه زمانی systemd timer ابزار بهتری است

ابزار cron در یک مورد خوب عمل می‌کند: اجرای یک دستور در یک زمان مشخص. در سایر موارد، ضعیف است. یک timer بدون نیاز به هیچ‌گونه redirect، لاگ‌ها را در journal ثبت می‌کند، وضعیت خروجی (exit status) را برای پرس‌وجو در اختیار شما می‌گذارد، امکان تنظیم ترتیب اجرا نسبت به network-online.target را فراهم می‌کند و با ایجاد تأخیر تصادفی، مانع از این می‌شود که صدها سرور همگی در یک ثانیه شروع به کار کنند. زمانی که وظیفه (job) شما به هر یک از این موارد نیاز داشته باشد، استفاده از یک systemd service و timer روی VPS نسبت به مدیریت یک خط در crontab، دردسر کمتری دارد. رفتار بازنشانی (retry) نیز متعلق به همین بخش است، زیرا سیاست‌های restart در systemd تعیین می‌کنند که پس از یک شکست چه اتفاقی بیفتد، در حالی که cron هیچ پاسخی برای این مسئله ندارد.

cron را برای کارهای کوچک حفظ کنید. هر چیزی که دارای وابستگی یا سیاست بازنشانی است را به یک timer منتقل کنید. هر دو می‌توانند روی یک سرور اجرا شوند، بنابراین این مهاجرتی نیست که مجبور باشید در یک نشست آن را به پایان برسانید.

FAQ

چرا کرون‌جاب من به‌صورت دستی کار می‌کند اما در cron اجرا نمی‌شود؟

زیرا محیط اجرای shell شما با محیط cron متفاوت است. shell ورود شما فایل‌های /etc/profile و ~/.bashrc را می‌خواند که متغیرهای PATH، تنظیمات محلی (locale) و متغیرهای agent شما را مقداردهی می‌کنند. cron دستور را بدون هیچ‌کدام از این‌ها، از یک دایرکتوری کاری متفاوت و گاهی با یک shell متفاوت اجرا می‌کند. برای تمام دستورات از مسیرهای مطلق (absolute paths) استفاده کنید، تنظیمات مورد نیاز خود را در ابتدای crontab یا داخل اسکریپت قرار دهید و یک job آزمایشی یک‌دقیقه‌ای زمان‌بندی کنید که خروجی env | sort، pwd و id را در یک فایل لاگ بنویسد تا بتوانید محیط واقعی cron را به‌جای حدس‌زدن، مشاهده کنید.

چگونه بررسی کنم که آیا cron واقعاً job مرا اجرا کرده است یا خیر؟

لاگ دیمون را بخوانید. در Debian و Ubuntu از journalctl -u cron و در Fedora، Rocky و Alma از journalctl -u crond استفاده کنید؛ برخی ایمیج‌ها پیام‌ها را از طریق rsyslog به فایلی در مسیر /var/log هدایت می‌کنند. به دنبال ورودی در دقیقه‌ای که زمان‌بندی کرده‌اید بگردید و بررسی کنید که آیا نام دستور شما در آن ذکر شده است یا خیر. نبود ورودی به این معنی است که cron زمان‌بندی را دریافت نکرده است، پس مطمئن شوید که crontab درستی را ویرایش کرده‌اید. ورودی بدون نتیجه به این معنی است که دستور شروع شده و بلافاصله متوقف شده است، پس خروجی آن را با یک redirect ذخیره کنید.

چرا date +%Y در داخل crontab باعث خطا می‌شود؟

cron کاراکتر % را در فیلد دستور به‌عنوان یک کاراکتر ویژه در نظر می‌گیرد. اولین % که escape نشده باشد، دستور را پایان می‌دهد؛ هر چیزی بعد از آن به‌عنوان ورودی استاندارد (stdin) به آن دستور ارسال می‌شود و هر % بعدی به یک خط جدید تبدیل می‌گردد. بنابراین نام فایلی که با فرمت تاریخ ساخته شده، هرگز به برنامه‌ای که برای آن نوشته‌اید نمی‌رسد. هر علامت درصد را با \% escape کنید یا دستور را به یک اسکریپت منتقل کرده و آن اسکریپت را از cron فراخوانی کنید، زیرا در داخل اسکریپت، علامت درصد معنای ویژه‌ای ندارد.

خروجی کرون‌جاب من کجا می‌رود؟

به سیستم ایمیل محلی، خطاب به مالک crontab یا هر مقصدی که در MAILTO تعیین شده است. اکثر ایمیج‌های VPS هیچ عامل انتقال ایمیلی (MTA) نصب‌شده‌ای ندارند، بنابراین پیام حذف می‌شود و به نظر می‌رسد job هیچ خروجی ندارد. خروجی را با استفاده از >> /path/to/log 2>&1 به یک فایل هدایت کنید؛ این ترتیب را رعایت کنید تا خطاهای استاندارد (stderr) به دنبال خروجی استاندارد (stdout) قرار گیرند، یا آن را از طریق logger -t myjob پایپ کرده و با journalctl -t myjob بخوانید. تا زمانی که در حال دیباگ هستید، از > /dev/null 2>&1 استفاده نکنید.

آیا باید از cron استفاده کنم یا از systemd timer؟

برای دستورات ساده در زمان‌های ثابت از cron استفاده کنید، به‌ویژه اگر ممکن است نیاز داشته باشید آن را به ماشینی منتقل کنید که systemd ندارد. زمانی از timer استفاده کنید که می‌خواهید خروجی بدون نیاز به redirect در journal ثبت شود، وضعیت خروجی (exit status) قابل پرس‌وجو باشد، ترتیب اجرا پس از بالا آمدن شبکه باشد، تأخیر تصادفی در شروع داشته باشید یا سیاست تلاش مجدد (retry policy) پس از شکست نیاز باشد. هر دو می‌توانند روی یک سرور اجرا شوند، بنابراین می‌توانید jobها را یکی‌یکی و در صورت نیاز منتقل کنید.