اجرای دستور در لینوکس پس از قطع شدن SSH
با قطع اتصال SSH سیگنال SIGHUP باعث توقف پردازشها میشود. برای جلوگیری از این مشکل از ابزارهای nohup، disown، tmux یا systemd-run استفاده کنید تا دستورات شما در پسزمینه زنده بمانند.
چرا دستور شما با قطع شدن SSH متوقف میشود
برای اینکه یک دستور پس از قطع شدن اتصال SSH همچنان اجرا شود، باید در جایی قرار بگیرد که سیگنال hangup به آن نرسد. هر یک از روشهای زیر راه متفاوتی برای دستیابی به این هدف است، بنابراین با درک مکانیزم آن شروع کنید.
نشست ورود شما روی یک pty (پایانه مجازی) اجرا میشود؛ یک دستگاه پایانه مجازی که sshd آن را روی سرور برای نشست شما ایجاد میکند. این پایانه، کنترلکننده shell شما و تمام دستوراتی است که از آن shell اجرا میکنید. اگر به جزئیات بیشتری نیاز دارید، آنچه SSH هنگام ورود تنظیم میکند را مطالعه کنید. وقتی اتصال TCP قطع میشود، sshd سمت خود را میبندد و pty از بین میرود. هسته سیستمعامل این اتفاق را به عنوان قطع شدن پایانه (hangup) تلقی میکند، بنابراین سیگنال SIGHUP را به گروه پردازش پیشزمینه آن پایانه و به رهبر نشست، یعنی همان shell شما، ارسال میکند. عمل پیشفرض برای SIGHUP پایان دادن به پردازش است. از آنجا که دستور شما در گروه پردازش پیشزمینه قرار داشت، دستور شما نیز متوقف میشود.
کارهای پسزمینه (background jobs) نیز ایمن نیستند. کاری که با & شروع میشود در گروه پردازش خاص خود قرار دارد، بنابراین هسته مستقیماً به آن سیگنال نمیفرستد، اما Bash این کار را انجام میدهد. با دریافت SIGHUP، یک bash تعاملی پیش از خروج، SIGHUP را برای تمام کارهای موجود در جدول خود ارسال میکند. از دید شما نتیجه یکسان است: کار از بین رفته و فایل لاگ در میانه خط متوقف میشود.
در اینجا یک عدم تقارن وجود دارد که باعث سردرگمی میشود. تایپ کردن exit باعث قطع شدن کارهای پسزمینه شما نمیشود، زیرا bash فقط زمانی این کار را انجام میدهد که گزینه huponexit فعال باشد و این گزینه بهصورت پیشفرض غیرفعال است. اما قطع شدن اتصال باعث توقف آنها میشود. کاری که هنگام بستن عادی پایانه زنده مانده بود، ممکن است با قطع شدن Wi-Fi از بین برود.
دو پیامد حاصل میشود که موضوع اصلی بحث ما هستند. پردازشی که SIGHUP را نادیده میگیرد، یا پردازشی که اصلاً پایانه کنترلی ندارد، متوقف نخواهد شد. اما پردازشی که خروجی استاندارد آن همچنان به pty از بین رفته اشاره دارد، جایی برای نوشتن ندارد: عملیات نوشتن با خطای EIO (خطای ورودی/خروجی) مواجه میشود و اکثر برنامهها در آن نقطه خارج میشوند. شما باید هر دو بخش را حل کنید. بسیاری از دستورالعملها فقط بخش اول را حل میکنند، به همین دلیل است که کاربران گزارش میدهند "nohup کار نکرد".
اگر اتصال شما چندین بار در روز قطع میشود، آن را نیز اصلاح کنید. ServerAliveInterval 60 در ~/.ssh/config از قطع شدن نشستهای غیرفعال توسط وقفه (timeout) در NAT (ترجمه آدرس شبکه) در مسیر جلوگیری میکند. نشستی که اصلاً برقرار نمیشود، خطای متفاوتی با دلایل متفاوت است که در آن تفاوت بین connection refused و connection timed out اهمیت پیدا میکند.
کدام روش باعث میشود یک دستور پس از قطع اتصال SSH همچنان اجرا شود؟
چهار پاسخ، مرتبشده بر اساس میزان اهمیت کار:
nohupیاsetsid: برای یک کار موردی که همین حالا شروع میکنید و بعداً لاگ آن را میخوانید. خروجی را باید شخصاً هدایت (redirect) کنید.disown: برای کاری که قبلاً شروع کردهاید و فراموش کردهاید از آن محافظت کنید. این ابزار پروسه را نجات میدهد، اما نمیتواند خروجی قبلی را به شما بازگرداند.tmuxیاscreen: برای کاری که نیاز دارید در طول چند روز آن را مشاهده کنید، متوقف کنید و دوباره به آن بازگردید.systemd-runیا یک unit file واقعی: برای هر چیزی که باید پس از خروج شما از سیستم همچنان فعال بماند، مانند یکrsyncششساعته یا وارد کردن دیتابیس در طول شب.
قانون مهمی که باید به خاطر بسپارید: اگر فراموش کردن یک کار باعث بروز مشکل میشود، آن کار متعلق به systemd است، نه tmux. پنجرهٔ tmux چیزی است که انسان باید آن را به خاطر بسپارد. اما یک unit دارای نام، وضعیت، لاگ و سیاست راهاندازی مجدد است که نفر بعدی بدون نیاز به توضیح، میتواند آن را پیدا کند.
دستورات nohup و setsid: اجرا کنید و رها کنید
nohup ./import.sh > ~/import.log 2>&1 &
echo $! > ~/import.pidدستور nohup وضعیت SIGHUP را روی نادیده گرفتن (ignore) تنظیم میکند و سپس دستور شما را اجرا میکند، بنابراین سیگنال hangup هسته به آن میرسد اما هیچ اثری ندارد. مسئولیت تغییر مسیر خروجی با شماست. اگر خروجی استاندارد را به ترمینال متصل باقی بگذارید، nohup آن را برای شما به nohup.out در دایرکتوری فعلی هدایت میکند، و در صورت عدم دسترسی، از $HOME/nohup.out استفاده کرده و پیام زیر را چاپ میکند:
nohup: ignoring input and appending output to 'nohup.out'ردیابی آن فایل دشوار است، بنابراین خودتان نامی برای آن انتخاب کنید. متغیر $! شامل PID (شناسه پردازش) آخرین کار در پسزمینه است و ذخیره کردن آن به شما اجازه میدهد پس از ورود مجدد به سیستم، وضعیت آن کار را بررسی کنید.
دستور setsid از زاویهای دیگر به همین مشکل میپردازد. این دستور، برنامه را در یک نشست (session) جدید و بدون ترمینال کنترلکننده اجرا میکند، بنابراین ترمینالی وجود ندارد که بتواند باعث قطع شدن آن شود.
setsid --fork ./import.sh > ~/import.log 2>&1از --fork استفاده کنید. بدون آن، setsid هرگاه پردازش از قبل رهبر گروه پردازشی نباشد (اتفاقی که در اسکریپتهای شل رخ میدهد)، setsid() را در همان محل فراخوانی میکند و در نتیجه اسکریپت شما در حالت مسدود باقی میماند. با استفاده از --fork، رفتار دستور در اسکریپت و در خط فرمان یکسان خواهد بود.
بررسی کنید که واقعاً چه نتیجهای گرفتهاید:
ps -o pid,ppid,sid,tty,stat,cmd -p "$(cat ~/import.pid)"ستون TTY با مقدار ? به این معنی است که پردازش هیچ ترمینال کنترلکنندهای ندارد، بنابراین هیچ چیزی نمیتواند باعث قطع شدن آن شود. در خروجی nohup، ستون TTY تا زمانی که متصل هستید چیزی شبیه به pts/0 را نشان میدهد و پس از نابودی pty به ? تبدیل میشود. هر دو نتیجه نشاندهنده سلامت پردازش هستند. کار شما با موفقیت باقی مانده است.
disown: نجات یک job که قبلاً شروع کردهاید
شما یک job دو ساعته را در foreground شروع کردهاید و حالا این مشکل را به یاد آوردهاید. آن را kill نکنید و دوباره شروع نکنید.
# press Ctrl-Z to suspend the job first
bg
jobs -l
disown -h %1Ctrl-Z آن job را متوقف (suspend) میکند، bg آن را در background ادامه میدهد و jobs -l شماره job را در کنار PID آن چاپ میکند. disown -h %1 آن job را علامتگذاری میکند تا bash سیگنال SIGHUP را برای آن ارسال نکند. دستور ساده disown %1 آن job را بهطور کامل از جدول bash حذف میکند که تأثیر مشابهی بر hangup دارد، اما پس از آن jobs دیگر آن را لیست نمیکند.
آنچه disown نمیتواند انجام دهد، جابهجایی خروجی است. پردازش همچنان pty را به عنوان خروجی استاندارد خود نگه میدارد و وقتی pty از بین برود، عملیات نوشتن بعدی مقدار EIO را برمیگرداند. بنابراین disown بهطور مطمئن یک job ساکت (مانند کامپایل که در یک فایل مینویسد) را نجات میدهد، اما اغلب یک job پرحرف را از دست میدهد. job یا زنده میماند و جایی برای چاپ ندارد، یا در خط بعدی خروجی خود میمیرد.
یک ابزار نجات برای توصیفگرهای فایل (file descriptors) وجود دارد. reptyr یک پردازش در حال اجرا را به ترمینال فعلی شما منتقل میکند: آن را با sudo apt install -y reptyr نصب کنید، سپس reptyr <pid> را از داخل یک پنجره tmux اجرا کنید. این ابزار از طریق ptrace کار میکند و Ubuntu شامل kernel.yama.ptrace_scope = 1 است که فقط اجازه trace کردن فرزندان خودتان را میدهد، بنابراین پردازشی که به ارث بردهاید به sudo reptyr <pid> نیاز دارد. با آن به عنوان یک ابزار اضطراری برخورد کنید. روال کاری خود را بر پایه آن بنا نکنید.
tmux: کاری که باید زیر نظر داشته باشید و به آن بازگردید
ابزار tmux (مخفف terminal multiplexer) مشکل را در لایهای متفاوت حل میکند. این ابزار بهجای محافظت از پردازش شما در برابر pty، یک pty در اختیار پردازش قرار میدهد که متعلق به نشست SSH شما نیست. سرور tmux خارج از آن نشست اجرا میشود و مالک ترمینالهای تمام پردازشهای درون خود است. اتصال SSH شما تنها یک نمایشگر متصل به آن است. اگر اتصال را قطع کنید، سرور متوجه این اتفاق نمیشود.
sudo apt update && sudo apt install -y tmux
tmux new -s importکار را در آن پنجره شروع کنید، سپس کلید Ctrl-b و پس از آن d را فشار دهید تا جدا (detach) شوید. بعداً دوباره وارد شوید و کار را ادامه دهید:
tmux ls
tmux attach -t importدستور tmux ls باید خطی را چاپ کند که با import: 1 windows شروع میشود. اگر خروجی no server running on /tmp/tmux-1000/default باشد، یعنی نشستی برای اتصال وجود ندارد؛ یا هرگز ایجاد نشده است و یا عاملی سرور را متوقف کرده است.
ابزار screen همان کار را با کلیدهای متفاوتی انجام میدهد. screen -S import یک نشست ایجاد میکند و Ctrl-a و سپس d از آن جدا میشود. screen -ls نشستهای موجود را فهرست میکند و screen -r import شما را به یکی از آنها بازمیگرداند. هر دو ابزار برای این کار مناسب هستند. کلید جدا شدن (detach) همان بخشی است که کاربران معمولاً فراموش میکنند.
یک multiplexer همچنین بهترین محیط برای کارهای تعاملی است که باید در برابر قطع سیگنال شبکه مقاوم باشند؛ به همین دلیل است که اجرای Claude Code روی یک VPS درون tmux پیکربندی استاندارد محسوب میشود و باعث میشود مدیریت نشست سرور با گوشی در شبکههای موبایلی که هر چند دقیقه یکبار قطع و وصل میشوند، امکانپذیر باشد.
systemd-run: واگذاری کار به PID 1
برای کاری که نباید هیچ وابستگی به شما داشته باشد، آن را به سیستم init بسپارید.
sudo systemd-run --unit=bigsync --collect /usr/bin/rsync -aH --stats /srv/data/ /mnt/backup/این دستور یک transient service unit با نام bigsync.service ایجاد میکند. این واحد cgroup اختصاصی خود را دارد، فاقد controlling terminal است و هیچ ارتباطی با نشست (session) ورود شما ندارد. دستور بلافاصله بازمیگردد و Running as unit: bigsync.service را چاپ میکند. وضعیت آن را با یکی از دستورات زیر پایش کنید:
systemctl status bigsync
journalctl -u bigsync -fفلگ --collect به systemd میگوید که واحد را پس از اتمام کار، حتی در صورت شکست، حذف کند. بدون این فلگ، یک transient unit شکستخورده در حافظه باقی میماند و نام آن اشغال میماند؛ در نتیجه اجرای بعدی با خطای وجود داشتن واحد (unit already exists) مواجه میشود. خروجی به journal ارسال میشود و هر خط دارای برچسب زمانی است. ورودیهای journal تنها زمانی پس از reboot باقی میمانند که دایرکتوری /var/log/journal وجود داشته باشد؛ بنابراین اگر این قابلیت را میخواهید، sudo mkdir -p /var/log/journal را اجرا کرده و systemd-journald را restart کنید.
به عنوان یک کاربر معمولی، اجرای systemd-run بدون sudo از polkit درخواست مجوز میکند و ==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units === را چاپ میکند. برای system units از sudo استفاده کنید.
شما همچنین میتوانید کار را تحت مدیریت کاربر خود اجرا کنید:
systemd-run --user --unit=bigsync --collect /usr/bin/rsync -aH /srv/data/ /mnt/backup/این روش یک دام دارد. مدیر سرویسهای کاربر شما، یعنی user@1000.service، معمولاً با پایان آخرین نشست شما متوقف میشود و تمام واحدهای کاربر را نیز با خود پایین میکشد. قابلیت lingering را یک بار فعال کنید:
loginctl enable-linger "$USER"
loginctl show-user "$USER" --property=Lingerدستور دوم باید Linger=yes را چاپ کند. با فعال بودن lingering، مدیر سرویسهای کاربر شما در زمان بوت شروع به کار میکند و فارغ از اینکه وارد سیستم شدهاید یا خیر، به اجرا ادامه میدهد. بدون این قابلیت، systemd-run --user هیچ مزیتی نسبت به nohup ندارد.
دستور systemd-run --scope موضوع متفاوتی است. این دستور برنامه را در foreground و متصل به ترمینال شما اجرا میکند، بنابراین در اینجا کمکی نمیکند.
برای هر کاری که بیش از یک بار اجرا میکنید، بهجای تایپ کردن یک transient unit در هر بار، آن را در قالب یک unit file بنویسید.
یک واحد دائمی برای کاری که دوباره اجرا خواهید کرد
[Unit]
Description=Nightly data sync
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
User=deploy
WorkingDirectory=/srv/data
ExecStart=/usr/local/bin/nightly-sync.shآن را با نام /etc/systemd/system/nightly-sync.service ذخیره کنید، sudo systemctl daemon-reload را اجرا کنید، سپس آن را با sudo systemctl start nightly-sync شروع کرده و با journalctl -u nightly-sync وضعیتش را بخوانید. زمانی که نیاز دارید کار به جای اجرا در لحظه، طبق زمانبندی اجرا شود، یک فایل .timer متناظر به آن اضافه کنید.
نوشتن یک systemd service unit و تایمر آن فرمت فایل و سینتکس زمانبندی را بهطور کامل پوشش میدهد.
خروجی به کجا میرود و چرا ناپدید میشود
ترتیب تغییر مسیرها (redirection) اهمیت دارد. > file 2>&1 خروجی استاندارد را به فایل هدایت میکند و سپس خطای استاندارد را به همان مکان میفرستد. 2>&1 > file این کار را برعکس انجام میدهد: خطای استاندارد همچنان به ترمینال میرود و ترمینال همان چیزی است که قرار است ناپدید شود. Bash همچنین از &> file برای هر دو جریان بهطور همزمان پشتیبانی میکند.
شگفتی دوم، بافرینگ (buffering) است. وقتی خروجی استاندارد یک ترمینال باشد، کتابخانه C هر خط را بلافاصله تخلیه (flush) میکند. وقتی خروجی استاندارد یک فایل باشد، به یک بافر بلوکی چند کیلوبایتی تغییر وضعیت میدهد، بنابراین tail -f ~/import.log برای دقایقی چیزی نشان نمیدهد و کار در حال اجرا، متوقفشده به نظر میرسد. بافرینگ خطی را با stdbuf -oL ./import.sh > ~/import.log 2>&1 اجباری کنید، یا از سوئیچ اختصاصی خود برنامه مانند python3 -u یا grep --line-buffered استفاده کنید.
از این الگو اجتناب کنید:
nohup ./import.sh 2>&1 | tee ~/import.log &nohup فقط از import.sh محافظت میکند و نه چیز دیگری. tee یک پردازش جداگانه در همان خط لوله (pipeline) است و همچنان با قطع اتصال (hangup) از بین میرود. در نتیجه import.sh در حال نوشتن در لولهای است که خوانندهای ندارد، بنابراین SIGPIPE را دریافت کرده و متوقف میشود. کل خط لوله را داخل setsid bash -c '...' قرار دهید، یا مستقیماً در فایل بنویسید و هنگام اتصال مجدد، tail -f را روی آن اجرا کنید.
یک جزئیات بیشتر بهطور خاص برای rsync: --info=progress2 جریانی از کاراکترهای بازگشت به ابتدای خط (carriage return) مینویسد که در ترمینال درست به نظر میرسد، اما در فایل لاگ یا ژورنال به یک خط بسیار طولانی تبدیل میشود. برای اجرای بدون نظارت (unattended)، آن را حذف کرده و بهجای آن از --stats استفاده کنید.
چرا کاری که در shell شما اجرا میشود، در systemd یا cron شکست میخورد
shell تعاملی شما فایلهای /etc/profile، ~/.profile و ~/.bashrc را میخواند، بنابراین به PATH، شیمهای مدیریت نسخه و متغیرهای export شدهٔ شما دسترسی دارد. یک unit در systemd هیچکدام از اینها را نمیخواند. cron نیز هیچکدام را نمیخواند: در Debian و Ubuntu، cron کارها را با SHELL=/bin/sh و PATH=/usr/bin:/bin اجرا میکند.
نشانهٔ این مشکل در systemd این است که systemctl status خطای (code=exited, status=203/EXEC) را گزارش میدهد؛ این یعنی systemd اصلاً نتوانسته فایل را اجرا کند، زیرا مسیر اشتباه بوده یا فایل به عنوان executable علامتگذاری نشده است. در cron، این خطا معمولاً به صورت command not found است که از طریق ایمیل محلی ارسال میشود، یا اگر سیستم ایمیلی نصب نباشد، اصلاً به جایی ارسال نمیشود.
پیش از آنکه یک ساعت وقت خود را صرف حدس زدن کنید، محیط اجرا را بررسی کنید:
sudo systemd-run --collect --wait --unit=envtest /usr/bin/env
journalctl -u envtest --no-pagerاین دستور دقیقاً محیطی را چاپ میکند که کار شما با آن اجرا خواهد شد. سپس شکاف موجود را برطرف کنید. برای هر چیزی که متعلق به خودتان است از مسیرهای مطلق (absolute paths) استفاده کنید، زیرا systemd یک rsync ساده را بر اساس لیست مسیرهای ثابت سیستم حل میکند و هرگز از PATH مربوط به shell شما استفاده نمیکند. متغیرهای مورد نیاز خود را با -p Environment="KEY=value" در خط فرمان یا با EnvironmentFile=/etc/default/myjob در فایل unit ارسال کنید. زمانی که یک کار واقعاً به محیط login شما نیاز دارد، آن را به عنوان /bin/bash -lc 'my-command' اجرا کنید و بپذیرید که آن کار اکنون به dotfileهای شما وابسته است.
چه چیزی همچنان باعث توقف یک job جداشده (detached) میشود
- راهاندازی مجدد (Reboot). هیچچیز در tmux پس از reboot باقی نمیماند، زیرا سرور tmux یک پردازش معمولی است و نشستها (sessions) وضعیت حافظهٔ آن هستند. بهروزرسانیهای هسته (Kernel) به معنای reboot است، بنابراین jobهایی که نمیتوانید بهسادگی دوباره اجرا کنید، باید در یک unit قرار بگیرند که با
systemctl enableمدیریت میشود. - قاتل حافظه (OOM Killer). دستور
dmesg -T | grep -i 'killed process'آن را نشان میدهد، از جمله نام پردازشی که انتخاب کرده است. یک import سنگین روی یک VPS کوچک، هدف متداولی برای این اتفاق است. - پاکسازی توسط logind. اگر در
/etc/systemd/logind.confگزینهKillUserProcesses=yesتنظیم شده باشد، پردازشهای باقیماندهٔ شما با پایان آخرین نشست (session) کشته میشوند و این شامل سرور tmux نیز میشود. تنظیمات فعلی را باloginctl show --property=KillUserProcessesبررسی کنید و کاربر خود را باloginctl enable-linger "$USER"از این قاعده مستثنی کنید. - پر شدن دیسک. job متوقف میشود چون فایلی که لاگ را به آن هدایت کردهاید، سیستم فایل را پر کرده است، نه به این دلیل که شما نشست را ترک کردهاید. پیش از آنکه سیگنالها را مقصر بدانید،
df -hرا اجرا کنید.
اجرای یک کار از طریق SSH بدون نیاز به حفظ اتصال
ssh vps 'sudo systemd-run --unit=import --collect /usr/local/bin/import.sh'دستور systemd-run بلافاصله پس از شروع unit بازمیگردد، بنابراین دستور ssh نیز خاتمه مییابد و کار مورد نظر هیچ اتصالی به نشست (session) راهانداز خود نخواهد داشت. این روش تمیزترین راه است.
نسخه nohup به دقت بیشتری نیاز دارد:
ssh vps 'nohup ./import.sh > ~/import.log 2>&1 < /dev/null &'بدون تغییر مسیر (redirection)، این دستور به نظر میرسد که معلق (hang) مانده است. sshd کانال را تا زمانی که هر فرآیندی خروجی استاندارد یا خطای استاندارد دستور راه دور را در اختیار داشته باشد باز نگه میدارد و یک کار در پسزمینه (backgrounded job) هر دو را به ارث میبرد. استفاده از nohup به تنهایی مشکل را حل نمیکند، زیرا nohup خروجی را تنها زمانی تغییر مسیر میدهد که خروجی یک ترمینال باشد، در حالی که اینجا خروجی یک pipe به سمت کلاینت شماست. افزودن < /dev/null سمت ورودی را نیز میبندد. ssh -n همین کار را از سمت کلاینت انجام میدهد.
FAQ
چرا وقتی اتصال SSH قطع میشود، دستور من متوقف میشود؟
پایانه مجازی (pty) که نشست شما از آن استفاده میکرد از بین میرود و هسته سیستمعامل سیگنال SIGHUP را به گروه پردازشهای پیشزمینه در آن پایانه ارسال میکند. اقدام پیشفرض برای SIGHUP، پایان دادن به پردازش است. کارهای پسزمینه نیز متوقف میشوند، زیرا bash پیش از خروج، سیگنال SIGHUP را به تمام کارهای موجود در جدول خود میفرستد. دستوری که SIGHUP را نادیده میگیرد، مانند دستوری که با nohup شروع شده باشد، یا دستوری که اصلاً از نشست شما استفاده نکرده باشد (مانند یک unit در systemd)، تحت تأثیر قرار نمیگیرد.
برای یک عملیات rsync ششساعته، tmux بهتر است یا systemd-run؟
systemd-run. یک نشست tmux به پردازش سروری وابسته است که شما شروع کردهاید، بنابراین با ریبوت بعدی پایان مییابد و برای هر کسی که نداند باید tmux ls را اجرا کند، نامرئی است. اجرای sudo systemd-run --unit=bigsync --collect /usr/bin/rsync -aH --stats /srv/data/ /mnt/backup/ به شما systemctl status bigsync برای وضعیت و journalctl -u bigsync برای خروجی میدهد که مدیر بعدی سیستم بدون نیاز به راهنمایی، هر دو را پیدا میکند. از tmux برای کارهایی استفاده کنید که نیاز دارید صفحه را ببینید و در آن تایپ کنید.
چگونه خروجی کاری را که فراموش کردهام تغییر مسیر (redirect) دهم، ببینم؟
معمولاً نمیتوانید، زیرا آن خروجی به پایانهای ارسال شده که دیگر وجود ندارد. تا زمانی که پردازش در حال اجراست، میتوانید فایلهای باز آن را با sudo ls -l /proc/<pid>/fd بررسی کنید یا فراخوانیهای سیستمی آن را با sudo strace -p <pid> مشاهده کنید، اما متنی که قبلاً نوشته شده از دست رفته است. reptyr <pid> میتواند پردازش را به یک پایانه جدید منتقل کند، و kernel.yama.ptrace_scope = 1 در اوبونتو به این معنی است که برای پردازشی که فرزند شما نیست، به sudo نیاز دارید. عادتی که از همه این مشکلات جلوگیری میکند، تغییر مسیر خروجی به یک فایل در همان ابتدا و استفاده از tail -f برای مشاهده آن فایل است.
آیا نشست جداشده (detached) در tmux پس از ریبوت باقی میماند؟
خیر. سرور tmux یک پردازش معمولی است و نشستها وضعیتهای درون حافظه آن هستند، بنابراین ریبوت باعث پایان یافتن هر دو میشود. همچنین وقتی /etc/systemd/logind.conf مقدار KillUserProcesses=yes را تنظیم میکند و شما از آخرین نشست خود خارج میشوید، tmux نیز از بین میرود که loginctl enable-linger "$USER" از این اتفاق جلوگیری میکند. برای کارهایی که باید پس از ریبوت بهطور خودکار بازگردند، یک unit برای systemd بنویسید و آن را systemctl enable کنید.
چرا اسکریپت من در shell اجرا میشود اما به عنوان یک unit در systemd شکست میخورد؟
یک unit فایلهای /etc/profile یا ~/.bashrc را نمیخواند، بنابراین نه اضافات PATH شما را دارد و نه متغیرهای export شده شما را. systemctl status که (code=exited, status=203/EXEC) را نشان میدهد به این معنی است که systemd اصلاً نتوانسته فایل را اجرا کند؛ بنابراین از مسیر مطلق (absolute path) استفاده کنید و مجوز اجرایی (executable bit) فایل را بررسی کنید. دستور sudo systemd-run --collect --wait --unit=envtest /usr/bin/env را اجرا کنید و خروجی آن را با journalctl -u envtest بخوانید تا دقیقاً بدانید محیط کاری که job شما دریافت میکند چیست. هر چیزی که کم است را با Environment= یا EnvironmentFile= تأمین کنید.