منبع اصلی نوشتار زیر در این لینک قرار دارد

tee کردن stdout اسکریپت در فایل log


می‌خواهم از داخل اسکریپت stdout را به یک فایل ثبت وقایع tee نمایم. و همچنین شاید stderr را.

این مورد نیازمند برخی دستکاری‌های مهارت‌آمیز توصیف‌گرفایل ، و یکی از موارد لوله دارای نام یا جایگزینی پردازشِ Bash می‌باشد. ما می‌خواهیم بر ترکیب دستوری Bashتمرکز نماییم.

اجازه بدهید با ساده‌ترین حالت شروع کنیم: می‌خواهم خروجی استانداردم را علاوه بر صفحه نمایش به یک فایل ثبت رخداد tee نمایم.

این به معنای آنست که از هر آنچه به stdout ارسال می‌گردد دو نسخه می‌خواهیم -- یک نسخه برای صفحه نمایش(یا هر جایی که stdout موقعی که اسکریپت شروع شده به آنجا اشاره می‌نمود)، و یک نسخه برای فایل ثبت وقایع. برنامه tee برای این منظور استفاده می‌شود:

# Bash
exec > >(tee mylog)    

ترکیب دستوری جایگزینی پردازش، یک لوله با نام (یا چیزی قابل قیاس با آن) ایجاد می‌کند و برنامه tee را با خواندن از آن لوله در پس زمینه اجرا می‌کند. tee دو کپی از هر آنچه می‌خواند، ایجاد می‌کند -- یکی برای فایل mylog (که آن را باز می‌کند)، و یکی برای stdout، که از اسکریپت ارث می‌برد. سرانجام، exec خروجی استاندارد پوسته را به لوله تغییر مسیر می‌دهد.

به علت اینکه یک job پس زمینه وجود دارد، که باید تمام خروجی را قبل از اینکه ما آن را ببینیم، بخواند و پردازش کند، این مورد مقداری تأخیر ناهماهنگ نشان می‌دهد. حالتی مانند این مورد را ملاحظه نمایید:

# Bash
exec > >(tee mylog)

echo "A" >&2
cat file
echo "B" >&2

سطر A و B که در stderr نوشته می‌شوند به میان پردازش tee نمی‌روند - آنها مستقیماً به stderr ارسال می‌شوند. به هرحال، file که ما از cat به دست می‌آوریم، قبل از اینکه ما آن را ببینیم به میان لوله و tee فرستاده می‌شود. اگر ما این اسکریپت را بدون هر گونه تغییر مسیر در ترمینال اجرا نماییم، احتمالاً (تضمین نمی‌شود!) چیزی مانند این می‌بینیم:زیرنویس مترجم 1

~$ ./foo
A
B
~$ hi mom

حقیقتاً راهی برای اجتناب از این وجود ندارد. ما می‌توانستیم با امید به خوش‌اقبالی، stderr را به سَبک مشابهی به تأخیر اندازیم، اما تضمینی وجود ندارد که سطرها به طورمساوی به تأخیر اُفتند.

همچنین، توجه نمایید که محتویات فایل بعد از اعلان بعدی پوسته چاپ گردید. بعضی اشخاص آن را اختلال نظم احساس می‌کنند. یکبار دیگر، روش پاکیزه‌ای برای اجتناب از آن وجود ندارد، چون tee در یک پردازش پس‌زمینه، اما خارج از کنترل ما انجام شده است. حتی اضافه نمودن فرمان wait به اسکریپت تأثیری ندارد. برخی افراد جهت دادن شانس به فرمان tee پس زمینه برای پایان یافتن، فرمان ‎sleep 1‎ را به انتهای اسکریپت اضافه می‌کنند. این کار می‌کند(معمولاً)، اما بعضی اشخاص آن را بد‌تر از مشکل اصلی می‌دانند.

اگر از دستور زبان Bash دوری کنیم، و لوله با نام و یک پردازش پس زمینه خودمان را تنظیم کنیم، آنوقت کنترل را به دست می‌آوریم:

# Bash
mkdir -p ~/tmp || exit 1
trap 'rm -f ~/tmp/pipe$$; exit' EXIT
mkfifo ~/tmp/pipe$$
tee mylog < ~/tmp/pipe$$ & pid=$!
exec > ~/tmp/pipe$$

echo A >&2
cat bar
echo B >&2

exec >&-
wait $pid

بازهم یک ناهمزمانی میان stdout و stderr وجود دارد، اما حداقل به اندازه‌ای نیست که بعد از آنکه اسکریپت خارج شده است در ترمینال بنویسد:

~$ ./foo
A
B
hi mom
~$

این به گونه بعدی این پرسش منجر می‌گردد -- می‌خواهم stdout و stderr با حفظ هماهنگی سطرها، هردو را با یکدیگر، ثبت کنم.

این یکی نسبتاً آسان است، به شرطی که دلواپس بهم‌ریختگی تمایز مابین stdout و stderr روی ترمینال نباشیم. ما فقط یکی از توصیف‌گرهای فایل را دوگانه می‌سازیم:

# Bash
exec > >(tee mylog) 2>&1

echo A >&2
cat file
echo B >&2

در حقیقت، حتی آسانتر از پرسش اصلی است. همه چیز به طور صحیح هماهنگ می‌شود، هم در ترمینال هم در فایل ثبت وقایع:

~$ ./foo
A
hi mom
B
~$ cat mylog
A
hi mom
B
~$ 

اگرچه، بازهم احتمال آمدن قسمتی از خروجی پس از اعلان فرمان پوسته وجود دارد:

~$ ./foo
A
hi mom
~$ B

(این مورد می‌تواند با همان راه حلِ لوله با نام و پردازش پس زمینه که قبلاً نشان دادم حل بشود.)

سومین گونه از این پرسش نیز نسبتاً ساده است: من می‌خواهم خروجی استاندارد را در یک فایل ثبت کنم، و stderr را در فایل دیگر. این ساده است، زیرا آن محدودیت اضافی را نداریم که می‌باید هماهنگی مابین دو جریان روی ترمینال را اداره نماییم. فقط نویسنده‌های فایل‌های log را تنظیم می‌کنیم:

exec > >(tee mylog.stdout) 2> >(tee mylog.stderr >&2)

echo A >&2
cat bar
echo B >&2

و اکنون جریانهای ما به طور جداگانه ثبت می‌شوند. چون فایلهای لاگ مجزا می‌باشند، ترتیبی که سطرها در آنها نوشته می‌شوند، اهمیت ندارد. هرچندکه، در ترمینال نتایجِ درهم ریخته حاصل خواهیم نمود:

~$ ./foo
A
hi mom
B
~$ ./foo
hi mom
A
B
~$

اما بعضی اشخاص نمی‌خواهند از دست دادن تمایز میان خروجی استاندارد و stderr، یا ناهماهنگی سطرها را قبول کنند. آنها در مورد کلمات وسواس دارند، و بنابراین متقاضی مشکل‌ترین گونه این پرسش هستند -- من می‌خواهم stdout و stderr را با یکدیگر داخل یک فایل منفرد ثبت نمایم، اما همچنین می‌خواهم آنها را در مقصدهای جداگانه اصلی آنها نگهداری کنم.

به منظور انجام این مورد، نخست باید چند نکته را متذکر شویم:

  • اگر آنها می‌خواهند دو جریان جداگانه stdout و stderr باشند، آنوقت پردازش معینی باید در هریک از آنها بنویسد.
  • راهی برای نوشتن یک پردازش در اسکریپت پوسته‌ای که از دو توصیف‌گر فایل جداگانه بخواند که یکی از آنها ورودی در دسترس دارد، وجود ندارد زیرا پوسته دارای میانجی ‎poll(2)‎ یا ‎select(2)‎ نیست.

  • بنابراین، ما به دو پردازش نویسنده جداگانه نیاز داریم.

  • تنها روش حفظ نمودن خروجی دو نویسنده جداگانه، از خراب شدن با یکدیگر، ایجاد اطمینان از آن است که هردو نویسنده، خروجی‌ خود را در وضعیت افزودن (درج) باز کنند. یک FD که در وضعیت افزودن باز می‌شود صفت تضمین کننده‌ای دارد، که هر گاه داده می‌خواهد در آن نوشته شود، نخست به انتها می‌پرد.

بنابراین:

# Bash
> mylog
exec > >(tee -a mylog) 2> >(tee -a mylog >&2)

echo A >&2
cat file
echo B >&2

این تضمین می‌کند که فایل ثبت رخداد صحیح است. تضمین نمی‌کند که نویسنده‌ها قبل از اعلان فرمان بعدی خاتمه یابند:

~$ ./foo
A
hi mom
B
~$ cat mylog
A
hi mom
B
~$ ./foo
A
hi mom
~$ B

می‌توانستیم همان ترفند لوله با نام بعلاوه waitرا استفاده کنیم که قبلاً به کار بردیم(به عنوان تمرین برای خواننده واگذار شد).

این صفحه پرسش «آیا سطرهایی که در ترمینال ظاهر می‌گردند تضمین می‌شود که به ترتیب صحیح ظاهر ‌شوند» را باقی می‌گذارد. در حال حاضر: حقیقتاً من نمی‌دانم.


CategoryShell

پرسش و پاسخ 106 (آخرین ویرایش ‎2013-03-28 16:00:45‎ توسط GreyCat)


  1. مترجم: در اینجا فرض شده است که شما دارای فایلی به نام file در پوشه جاری هستید که محتوی رشته hi mom می‌باشد و همچنین کد فوق را در اسکریپتی به نام foo ذخیره نموده‌اید. پس از اجرای اسکریپت فایلی به نام mylog با همان محتوا در دایرکتوری جاری خواهید داشت، و این نشان دهنده آن است که محتوای فایل file در دونسخه یکی برای خروجی استاندارد و دیگری به فایل mylog ارسال گردیده است. (بازگشت)