쉘에서 파일 디스크립터를 사용한 필터 구현

쉘에서 파일 디스크립터 사용하기

입출력을 반복하는 형태의 커뮤니케이션을 구현하기 위해서는 한 쌍의 FIFO 파이프를 만들어서 사용한다. 파이프는 mkfifo 명령을 통해서 이름이 붙은 파이프를 만들 수 있다. (이름이 붙은 파이프라는 것은 결국 임시파일이다.)

mkfifo /tmp/infifo
mafifo /tmp/outfifo

입력을 sed를 이용해 필터링하여 출력하는 예제는 다음과 같다.

이 예제들은 msys bash에서는 동작하지 않는다. 파일디스크립터를 윈도가 지원하지 않기 때문으로 보인다.

mkfifo /tmp/outfifo
sed 's/a/b/' < /tmp/outfifo &
echo "This is a test" > /tmp/outfifo

sed는 fifo로 부터 받은 문자열을 출력하도록 실행되고, echo는 그 이후에 fifo로 문장열을 리다이렉션한다. echo 명령이 출력 FIFO로 데이터를 쓰면, 쓴 직후에 파일이 닫힌다. 그러면 sed 명령은 SIGPIPE 시그널을 받고 (별도의 처리가 없으니) 종료된다. 만약 필터를 제대로 사용하려면 FIFO가 필터에서 사용될때까지 닫혀서는 안된다. 이를 구현하기 위해서는 파일 디스크립터를 사용해야 한다.

입출력을 위해 파일 디스크립터 사용하기

앞서 설명한바와 같이 이름을 붙인 파이프로 명령을 보내는 것은, 첫번째 메시지가 전달된 후에 명령이 종료된다. 이를 방지하기 위해서는 파일 디스크립터를 열어 쉘에게 이를 지속적으로 액세스할 수 있도록 해야 한다.

우선, 출력을 위한 디스크립터는 다음과 같은 식으로 만든다.

exec 8> /tmp/outfifo

이 때, 0, 1, 2는 표준 디스크립터로 이미 사용되고 있다. 위 예는 안전을 위해 8번을 사용한다.

읽기 디스크립터는 (좀 이상한 모양인데) 다음과 같이…

exec 9<> /tmp/infifo

이제 8, 9번 디스크립터가 열렸다.

파일 디스크립터로는 다음과 같이

echo "This is a tst." >&8
read MYLINE <&9

파일처럼 내용을 넣고 뺀다. 각 버퍼는 일종의 큐처럼 동작한다고 보면된다.

간단히 연산자를 소개하자면

  • n<> : 입출력 디스크립터
  • n> : 출력전용 디스크립터
  • n>> : Appeding 가능한 디스크립터 (언제 쓰는 건지는 모르겠다.)
  • n<& o, n>& o : 사실 이 둘은 동일한 표현이다. 다만 가독성을 위해서 입출력 방향에 맞게 쓰는 것을 권장한다.
  • n<&-, n>&- : 디스크립터를 닫는다.

다음 예제는 입출력 필터를 온전한 형태로 사용하는 예이다. sed는 이미 자체적으로 입력 버퍼를 가지고 있기 때문에 필터로 사용하려면 이 자체 버퍼를 사용하지 않도록 해야 한다. 이 옵션은 ‘-l’인데, GNU sed는 ‘-u’로 되어있음을 주의한다.

#!/bin/sh
#
### 두개의 FIFO를 만든다. 
INFIFO="./infifo.$$"
OUTFIFO="./outfifo.$$"
### 이 때, "$$"는 현재 프로세스 번호 
#
#   sed 옵션 문자열
BUFFER_FLAG="-l"
#   만약, GNU sed면 옵션 문자열 변경
if [ "x$(sed --version | grep GNU)" != "x" ] ; then
    BUFFER_FLAG="-u"
fi
### 필터 실행
sed $BUFFER_FLAG 's/a test/not a test/' < $INFIFO > $OUTFIFO
PID=$!
#
### 각 FIFO에 대해서 파일 디스크립터를 연다. 
exec 8> $INFIFO
ecec 9<> $OUTFIFO
#
### 파일 디스크립터로 문자열을 보내본다.
echo "This is a test." >& 8
### 이렇게 보내진 문자열은 sed에 의해 필터링 되어 9번 디스크립터로 내보내진다. 
read A <& 9
echo "Result 1: $A"
#
# 반복
read A <& 9
echo "Result 2: $A"
read A <& 9
echo "Result 3: $A"
#
# sed는 실행중인가? 
ps -p $PID
#
# 디스크립터 닫기
exec 8>&-
exec 9<&-
#
# 디스크립터가 닫히면 파이프가 닫히고, sed도 종료된다. 
ps -p $PID
# 임시 파일 삭제
rm "$INFIFO"
rm "$OUTFIFO"