17장. Here Documents

here codumentI/O 재지향의 특별한 형태로서, ftptelnet, ex처럼 사용자와 입력을 주고 받는(interactive) 프로그램에게 명령어 스크립트를 입력시키는데 쓰입니다. 프로그램의 입력으로 쓸 명령어 리스트들로 이루어진 스크립트는 보통 제한 문자열(limit string)로 표현됩니다. 특별한 심볼인 << 뒤에 제한 문자열을 적어 줍니다. "here document"command-file
command #1
command #2
...
등이 포함되어 있다고 했을 때, interactive-program < command-file 이라고 해서 파일의 출력을 프로그램으로 재지향 시키는 것과 비슷한 결과를 가져 옵니다.

"here document"로는 이렇게 할 수 있습니다:
#!/bin/bash
interactive-program <<LimitString
command #1
command #2
...
LimitString

어떤 명령어 목록에서도 나타나지 않을 만큼 충분히 일반적이지 않은 제한 문자열을 골라야만 복잡한 상황을 피할 수 있습니다.

here documents는 비대화형 모드(non-interactive)로 도는 유틸리티나 명령어와 쓸 때 더 좋은 효과가 나타난다는 것에 주의하세요.

예 17-1. dummyfile: 두 줄짜리 더미 파일 만들기

#!/bin/bash

# 파일 편집을 위해 'vi'를 비대화 모드로 사용.
# ('vim'으로 하면 몇 가지 이유때문에 안 됩니다.)
# 'sed'를 에뮬레이트함.

E_BADARGS=65

if [ -z "$1" ]
then
  echo "사용법: `basename $0` filename"
  exit $E_BADARGS
fi

TARGETFILE=$1

# 파일에 두 줄을 삽입한 다음 저장.
#--------here document 시작-----------#
vi $TARGETFILE <<x23LimitStringx23
i
예제 파일의 첫번째 줄입니다.
예제 파일의 두번째 줄입니다.
^[
ZZ
x23LimitStringx23
#----------here document 끝-----------#

# 위에서 ^[ 는 Control-V 와 Escape 를 순서대로 눌렀을 때 나타나는 표시입니다.

exit 0

위의 스크립트는 vi보다 ex를 써서 구현했다면 더 효과적이었을 겁니다. ex 명령어의 목록을 갖고 있는 Here document를 ex 스크립트이라고 부르는데 이들은 충분히 자신만의 카테고리를 만들 수 있습니다.

예 17-2. broadcast: 로그인 해 있는 모든 사람들에게 메세지 보내기

#!/bin/bash

wall <<zzz23EndOfMessagezzz23
점심에 피자 먹습니다. 주문 하실 분은 시스템 관리자한테 이메일을 보내세요.
    (멸치(anchovy, 옮긴이: 윽, 이런 토핑도 있나요?)나 버섯 토핑을 추가하실 분은 돈을 더 내셔야 합니다.)
# 메세지가 더 있다면 여기에 적으면 됩니다.
# 주의: 'wall'은 이 주석들도 메세지로 내보냅니다.
zzz23EndOfMessagezzz23

# wall <message-file 처럼 해서 더 효과적으로 할 수 있습니다.
# 어쨌든, 스크립트에서 메세지 형태를 결정해 두면, 손이 덜 갑니다.

exit 0

예 17-3. cat으로 여러 줄의 메세지 만들기

#!/bin/bash

# 'echo' 는 한 줄짜리 메세지를 찍기에 아주 좋습니다만,
# 여러줄의 메세지를 찍으려면 문제가 많습니다.
# here document 를 이용한 'cat'을 쓰면 이 문제가 해결됩니다.

cat <<End-of-message
-------------------------------------
메세지의 첫 번째줄입니다.
메세지의 두 번째줄입니다.
메세지의 세 번째줄입니다.
메세지의 네 번째줄입니다.
메세지의 마지막 줄입니다.
-------------------------------------
End-of-message

exit 0


#-------------------------------------------------------------
# 위에서 "exit 0"을 했기 때문에 다음 코드는 실행되지 않습니다.

# S.C. 가 다음처럼 하는 것도 가능하다는 것을 지적해 주었습니다.
echo "-------------------------------------
메세지의 첫 번째줄입니다.
메세지의 두 번째줄입니다.
메세지의 세 번째줄입니다.
메세지의 네 번째줄입니다.
메세지의 마지막 줄입니다.
-------------------------------------"
# 하지만 'echo'의 내용이 이스케이프되지 않으면 큰따옴표로 쿼우트되지 않아도 됩니다.

here document의 제한 문자열에 - 옵션을 붙이면(<<-LimitString) 출력에서 탭을 지워 줍니다(빈 칸은 아님). 이걸 쓰면 스크립트를 읽기가 좋아집니다.

예 17-4. 탭이 지워진 여러 줄의 메세지

#!/bin/bash
# 앞의 예제와 똑같지만...

#  here document 에 - 옵션을 주면(<<-)
#  문서안에 들어 있는 탭을 무시해 줍니다. 하지만 빈 칸은 *아닙니다*.

cat <<-ENDOFMESSAGE
	메세지의 첫 번째줄입니다.
	메세지의 두 번째줄입니다.
	메세지의 세 번째줄입니다.
	메세지의 네 번째줄입니다.
	메세지의 마지막 줄입니다.
ENDOFMESSAGE
# 이 스크립트의 출력은 왼쪽에 붙어서 나옵니다.
# 메세지의 앞에 들어 있는 탭은 보이지 않습니다.

# 위의 5줄짜리 "message"는 앞에 빈 칸이 아니라 탭이 들어 있습니다.
# 빈 칸은 <<- 의 영향을 받지 않습니다.


exit 0

here document는 매개변수 치환과 명령어 치환을 지원합니다. 따라서 매개변수에 따라 다른 출력을 얻을 수 있습니다.

예 17-5. Here document에서 매개변수 치환하기

#!/bin/bash
# 매개변수 치환을 쓰는 다른 'cat' here document.

# 명령어줄 인자없이 실행시켜 보세요,              ./scriptname
# 명령어줄 인자를 한 개만 줘서 실행시켜 보세요,   ./scriptname Mortimer
# 두 낱말을 쿼우트해서 명령어줄 인자로 줘보세요,  ./scriptname "Mortimer Jones"

CMDLINEPARAM=1     # 최소한의 명령어줄 인자.

if [ $# -ge $CMDLINEPARAM ]
then
  NAME=$1          # 명령어줄 인자가 하나 이상이라면,
                   # 그냥 첫번째 인자만 받아들임.
else
  NAME="John Doe"  # 명령어줄 인자가 없을 때의 기본값.
fi  

RESPONDENT="이 멋진 스크립트의 저자"  
  

cat <<Endofmessage

안녕하세요. $NAME 씨.
전 $RESPONDENT 인데요, 만나서 반갑습니다. $NAME 씨.

# 이 주석도 출력됩니다(왜일까요?).

Endofmessage

# 빈 칸도 출력되기 때문에 "주석"도 출력됩니다. 주의하세요.

exit 0

here document의 앞에 나오는 "제한 문자열"을 쿼우트 해주거나 이스케이프 시켜 주면 내부에서 매개변수 치환이 못 일어나게 해 줍니다. 이건 거의 쓸모가 없겠죠.

예 17-6. 매개변수 치환 끄기

#!/bin/bash
#  매개변수 치환을 끈 'cat' here document.

NAME="John Doe"
RESPONDENT="이 멋진 스크립트의 저자"  

cat <<'Endofmessage'

안녕하세요, $NAME 씨.
전 $RESPONDENT 인데요, 만나서 반갑습니다. $NAME 씨.

Endofmessage

# "제한 문자열"(limit string)을 쿼우트 해 주거나 이스케이프 시키면
# 매개변수 치환이 일어나지 않습니다.
# here document 시작부분에서 
#  cat <<"Endofmessage"
#  cat <<\Endofmessage
# 이라고 해도 똑같은 결과가 나옵니다.


다음은 매개변수 치환을 쓰는 here document가 들어 있는 유용한 스크립트입니다.

예 17-7. upload: "Sunsite" incoming 디렉토리에 파일 한 쌍을 업로드

#!/bin/bash
# upload.sh

# 파일 두 개(Filename.lsm, Filename.tar.gz)를
# Sunsite(metalab.unc.edu)의 incoming 디렉토리로 업로드.

E_ARGERROR=65

if [ -z "$1" ]
then
  echo "사용법: `basename $0` filename"
  exit $E_ARGERROR
fi  


Filename=`basename $1`           # 파일이름에서 경로명을 떼어내고,

Server="metalab.unc.edu"
Directory="/incoming/Linux"
# 이렇게 하드코딩할 필요는 없고,
# 명령어줄 인자로 처리하도록 할 수도 있습니다.

Password="your.e-mail.address"   # 알맞게 고치세요.

ftp -n $Server <<End-Of-Session
# -n 옵션은 자동 로그인을 막아줍니다.

user anonymous "$Password"
binary
bell                # 각 파일 전송이 끝날때마다 '벨'을 울려줍니다.
cd $Directory
put "$Filename.lsm"
put "$Filename.tar.gz"
bye
End-Of-Session

exit 0

:를 더미(dummy) 명령어로 써서 here document의 출력을 받아 들이게 할 수 있습니다. 이렇게 하면 실제로는 "아무개"(anonymous) here document를 만들어 냅니다.

예 17-8. "아무개"(anonymous) Here Document

#!/bin/bash

: <<TESTVARIABLES
${HOSTNAME?}${USER?}${MAIL?}  # 변수중 하나라도 세트가 안 돼 있으면 에러 메세지를 출력.
TESTVARIABLES

exit 0

참고: Here documents는 임시 파일을 만들지만 사용후 지워지기 때문에 다른 프로세스가 접근할 수 없습니다.

bash$ bash -c 'lsof -a -p $$ -d0' << EOF
> EOF
lsof    1213 bozo    0r   REG    3,5    0 30386 /tmp/t1213-0-sh (deleted)
          

경고

몇몇 유틸리티들은 here document 안에서 제대로 동작하지 않습니다.

"here document"로 하기에 너무 복잡한 작업들은 expect 스크립트 언어를 고려해 보기 바랍니다. expect는 사용자와 입력을 주고 받는(interactive) 프로그램에게 원하는대로 입력을 넣어줄 수 있게 해 주는 스크립트 언어입니다.