부록 D. I/O와 I/O 재지향에 대한 자세한 소개

Stephane Chazelas가 작성, 본 문서의 저자가 교정

어떤 명령어든 처음 세 개의 파일 디스크립터가 사용 가능하기를 기대합니다. 첫 번째는 읽기용인 fd 0(표준입력)이고, 나머지 둘은 쓰기용으로 fd 1fd 2입니다.

표준입력(stdin), 표준출력(stdout), 표준에러(stderr)는 각 명령어들과 연관이 있습니다. ls 2>&1ls 명령어의 표준에러를 임시로 쉘의 표준출력과 동일한 "리소스"로 연결시켜 줍니다.

관습적으로 한 명령어는 fd 0 (표준입력)에서 입력을 읽고, fd 1 (표준출력)으로 보통의 결과들을 출력하고, 에러 출력은 fs 2 (표준에러)로 합니다. 만약에 이들중 하나라도 열려 있지 않다면 문제가 생길지도 모릅니다.

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

예를 들어, xterm을 실행시키면 먼저 자신을 초기화하고 사용자 쉘을 띄우기 전에 터미널 디바이스(/dev/pts/<n> 나 비슷한것)를 세 번 엽니다.

이 시점에서 Bash 는 이 세 개의 파일 디스크립터들을 상속받고 Bash 에서 실행시키는 각각의 명령어(자식 프로세스)들은 다시 차례대로 이 디스크립터들을 상속받습니다. 재지향이란 이 파일 디스크립터중의 하나를 다른 파일(혹은 파이프나 사용 가능한 모든 것)로 재할당 시키는 것입니다. 파일 디스크립터는 지역적(locally)으로 재할당 될 수도 있고(명령어, 명령어 그룹, 서브쉘, while, if, case, for 루프 등을 위해서), 쉘의 나머지 부분을 위해서 전역적(globally)으로 될 수도 있습니다(exec를 써서).

ls > /dev/nullls의 fs 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


# 각 명령어와 서브쉘에 대해서 어떤 fs 가 무엇을 가르키는지를 알아내 보세요.

exit 0