20장. 서브쉘(Subshells)

여러분이 쉘 스크립트를 실행시키게 되면 다른 명령어 프로세스의 인스턴스를 띄웁니다. 여러분이 실행시킨 명령어가 명령어 줄 프롬프트에서 해석될 때, 스크립트 파일에 들어 있는 명령어 목록도 한 번에 같이 처리하게 됩니다. 이 때 실행되는 각각의 쉘 스크립트는 사실, 콘솔이나 한텀에서 여러분에게 프롬프트를 보여주던 부모 쉘의 서브 프로세스입니다.

쉘 스크립트 자신도 서브 프로세스를 띄울 수 있습니다. 이 서브쉘 덕분에 스크립트가 병렬로 처리되고 결국 동시에 다중 작업을 하는 것과 동일한 결과를 가져옵니다.

병렬적인 명령어 목록

( command1; command2; command3; ... )

중괄호 속에 들어 있는 명령어들은 서브쉘로 돌게 됩니다.

참고: 서브쉘의 변수들은 서브쉘이 속해 있는 코드 블럭 밖으로 보이지 않습니다. 이 변수들은 부모 프로세스나 그 서브쉘을 띄운 쉘에서 접근할 수 없기 때문에 실제로는 지역 변수가 됩니다.

예 20-1. 서브쉘에서 변수의 통용 범위(variable scope)

#!/bin/bash
# subshell.sh

echo

outer_variable=Outer

(
inner_variable=Inner
echo "서브쉘의 \"inner_variable\" = $inner_variable"
echo "서브쉘의 \"outer\" = $outer_variable"
)

echo

if [ -z "$inner_variable" ]
then
  echo "inner_variable 은 쉘의 메인에서 정의되지 않았습니다."
else
  echo "inner_variable 은 쉘의 메인에서 정의되었습니다."
fi

echo "쉘 메인의 \"inner_variable\" = $inner_variable"
# $inner_variable 은 초기화되지 않은 것처럼 보이는데,
# 서브쉘에서 정의된 변수는 그 서브쉘의 "지역 변수"이기 때문입니다.

echo

exit 0

예 32-1 참고.

+

서브쉘에서 작업 디렉토리가 변경돼도, 부모 쉘에게 영향을 미치지 않습니다.

예 20-2. 사용자 프로파일 보기

#!/bin/bash
# allprofs.sh: 모든 사용자의 프로파일 출력

# 이 스크립트는 Heiner Steven 이 작성하고, 이 문서의 저자가 수정했습니다.

FILE=.bashrc  #  사용자 프로파일을 담고 있는 파일.
              #+ 원래 스크립트에서는 ".profile" 이었습니다.

for home in `awk -F: '{print $6}' /etc/passwd`
do
  [ -d "$home" ] || continue    # 홈 디렉토리가 없는 사용자라면 다음으로 넘어감.
  [ -r "$home" ] || continue    # 읽을수 없는 홈디렉토리라면 역시 그냥 넘어감.
  (cd $home; [ -e $FILE ] && less $FILE)
done

# 'cd $home' 이 서브쉘에서 돌기 때문에,
#+ 스크립트가 끝난 다음, 'cd'를 써서 원래 디렉토리로 다시 돌아갈 필요가 없습니다.

exit 0

서브쉘은 특정 명령어 그룹에 그들만의 "전용 환경"(dedicated environment)을 설정해 줄 수 있습니다.
COMMAND1
COMMAND2
COMMAND3
(
  IFS=:
  PATH=/bin
  unset TERMINFO
  set -C
  shift 5
  COMMAND4
  COMMAND5
  exit 3 # 서브쉘만 종료시킴.
)
# 부모 쉘의 환경은 영향을 받지 않고 그대로 남아 있습니다.
COMMAND6
COMMAND7
변수가 정의됐는지 아닌지를 확인해 보는데 이걸 응용해 볼 수 있습니다.
if (set -u; : $variable) 2> /dev/null
then
  echo "변수가 세트되어 있습니다."
fi

# 이렇게도 할 수 있죠.  [[ ${variable-x} != x || ${variable-y} != y ]]
# 혹은                  [[ ${variable-x} != x$variable ]]
# 역시                  [[ ${variable+x} = x ]])
잠금 파일을 확인하는데도 응용해 볼 수 있습니다:
if (set -C; : > lock_file) 2> /dev/null
then
  echo "다른 사용자가 이미 실행중입니다."
  exit 65
fi

# Thanks, S.C.

프로세스를 다른 서브쉘에서 병렬로 실행시킬 수도 있습니다. 이렇게 하면 복잡한 작업을 여러개로 나누어서 동시에 처리할 수 있습니다.

예 20-3. 프로세스를 서브쉘에서 병렬로 돌리기

    (cat list1 list2 list3 | sort | uniq > list123) &
    (cat list4 list5 list6 | sort | uniq > list456) &
    # 동시에 두 종류의 파일들을 합치고 정렬.
    # 백그라운드로 돌려서 병렬로 확실히 수행되도록 함.
    #
    # 다음과 같은 결과.
    #   cat list1 list2 list3 | sort | uniq > list123 &
    #   cat list4 list5 list6 | sort | uniq > list456 &

    wait   # 서브쉘이 끝나기 전에 다음 명령어를 실행하지 않게 함.

    diff list123 list456

서브쉘로 I/O 재지향을 하려면 ls -al | (command) 처럼 파이프 연산자인 "|"를 씁니다.

참고: 중괄호속의 명령어 블럭은 서브쉘을 띄우지 않습니다.

{ command1; command2; command3; ... }