پیوست ‎F‎- یک مقدمه تفصیلی برای ورودی/خروجی و تغییر مسیر ‎I/O‎

نوشته شده به وسیله ‎Stéphane Chazelas‎، و بازنگری توسط نگارنده این سند

هر فرمان انتظار دارد که ابتدا سه توصیف‌گر فایل در دسترس باشد. نخست، ‎fd 0‎ (ورودی استاندارد، stdin)، برای خواندن است. دو توصیف‌گر دیگر (‎fd 1‎، یا stdout و ‎fd 2‎، یا stderr) برای نوشتن هستند.

با هر فرمان یک stdin،‏ stdout، و یک stderr مرتبط وجود دارد. ‎ls 2>&1‎ یعنی متصل شدن موقت stderr فرمان ls به همان «منبعی» که stdout پوسته است.

طبق قرار داد، یک فرمان ورودی‌اش را از ‎fd‎ شماره ‎0‎ ‏(stdin) می‌خواند، خروجی عادی‌اش را در ‎fd‎ شماره ‎1‎ ‏(stdout)، و خروجی خطا را در ‎fd‎ شماره ‎2‎ ‏(stderr) می‌نویسد. اگر یکی از این سه ‎fd‎ باز نباشد، ممکن است شما با مشکلات مواجه بشوید:

bash$ cat /etc/passwd >&-
cat: standard output: Bad file descriptor

برای مثال، وقتی xterm اجرا می‌شود، ابتدا خودش را مقداردهی می‌کند. قبل از اجرای پوسته کاربر، xterm سه بار دستگاه ترمینال (‎/dev/pts/<n>‎ یا چیزی مشابه آن) را باز می‌کند.

در این نقطه، Bash این سه توصیف‌گر فایل را به ارث می‌برد، و هر فرمان (پردازش فرزند) اجرا شده به وسیله Bash به نوبت خود آنها را به ارث می‌برد، مگر موقعی که شما فرمان را تغییر مسیر بدهید. تغییر مسیر یعنی دوباره تخصیص دادن یکی از توصیف‌گرهای فایل به یک فایل دیگر (یا لوله، یا هر چیز مجاز). توصیف‌گرهای فایل می‌توانند به طور محلی (برای یک فرمان، یک گروه فرمان، یک پوسته فرعی، یک حلقه while یا for یا if یا case...)، یا به طور سراسری برای بقیه پوسته (با استفاده از exec) دوباره تخصیص داده شوند.

‎ls > /dev/null‎ یعنی اجرای ls در حالیکه ‎fd 1‎ آن به ‎/dev/null‎ متصل شده باشد.

bash$ lsof -a -p $$ -d0,1,2
 COMMAND PID     USER   FD   TYPE DEVICE SIZE NODE NAME
 bash    363 bozo        0u   CHR  136,1         3 /dev/pts/1
 bash    363 bozo        1u   CHR  136,1         3 /dev/pts/1
 bash    363 bozo        2u   CHR  136,1         3 /dev/pts/1

bash$ exec 2> /dev/null
bash$ lsof -a -p $$ -d0,1,2
 COMMAND PID     USER   FD   TYPE DEVICE SIZE NODE NAME
 bash    371 bozo        0u   CHR  136,1         3 /dev/pts/1
 bash    371 bozo        1u   CHR  136,1         3 /dev/pts/1
 bash    371 bozo        2w   CHR    1,3       120 /dev/null

bash$ bash -c 'lsof -a -p $$ -d0,1,2' | cat
 COMMAND PID USER   FD   TYPE DEVICE SIZE NODE NAME
 lsof    379 root    0u   CHR  136,1         3 /dev/pts/1
 lsof    379 root    1w  FIFO    0,0      7118 pipe
 lsof    379 root    2u   CHR  136,1         3 /dev/pts/1

bash$ echo "$(bash -c 'lsof -a -p $$ -d0,1,2' 2>&1)"
 COMMAND PID USER   FD   TYPE DEVICE SIZE NODE NAME
 lsof    426 root    0u   CHR  136,1         3 /dev/pts/1
 lsof    426 root    1w  FIFO    0,0      7520 pipe
 lsof    426 root    2w  FIFO    0,0      7520 pipe

این برای انواع مختلف تغییر مسیر کار می‌کند.

تمرین: اسکریپت زیر را تجزیه و تحلیل نمایید.

#! /usr/bin/env bash
mkfifo /tmp/fifo1 /tmp/fifo2
while read a; do echo "FIFO1: $a"; done < /tmp/fifo1 & exec 7> /tmp/fifo1
exec 8> >(while read a; do echo "FD8: $a, to fd7"; done >&7)
exec 3>&1
(
 (
  (
   while read a; do echo "FIFO2: $a"; done < /tmp/fifo2 | tee /dev/stderr \
   | tee /dev/fd/4 | tee /dev/fd/5 | tee /dev/fd/6 >&7 & exec 3> /tmp/fifo2

   echo 1st, to stdout
   sleep 1
   echo 2nd, to stderr >&2
   sleep 1
   echo 3rd, to fd 3 >&3
   sleep 1
   echo 4th, to fd 4 >&4
   sleep 1
   echo 5th, to fd 5 >&5
   sleep 1
   echo 6th, through a pipe | sed 's/.*/PIPE: &, to fd 5/' >&5
   sleep 1
   echo 7th, to fd 6 >&6
   sleep 1
   echo 8th, to fd 7 >&7
   sleep 1
   echo 9th, to fd 8 >&8

  ) 4>&1 >&3 3>&- | while read a; do echo "FD4: $a"; done 1>&3 5>&- 6>&-
 ) 5>&1 >&3 | while read a; do echo "FD5: $a"; done 1>&3 6>&-
) 6>&1 >&3 | while read a; do echo "FD6: $a"; done 3>&-

rm -f /tmp/fifo1 /tmp/fifo2
# برای هر فرمان و هر پوسته فرعی، معین کنید که ‎fd‎ به چه اشاره می‌کند.
# خوش باشید!
exit 0