Skip to content

Instantly share code, notes, and snippets.

@YangSiJun528
Created January 15, 2026 05:35
Show Gist options
  • Select an option

  • Save YangSiJun528/fe348f852be3c539af389d446382de23 to your computer and use it in GitHub Desktop.

Select an option

Save YangSiJun528/fe348f852be3c539af389d446382de23 to your computer and use it in GitHub Desktop.

원본: https://tldp.org/LDP/abs/html/subshells.html


21장. 서브셸(Subshells)

쉘 스크립트를 실행하면 새로운 프로세스인 서브셸이 실행됩니다.

정의: 서브셸은 쉘(또는 쉘 스크립트)에 의해 실행된 자식 프로세스입니다.

서브셸은 명령어 프로세서, 즉 콘솔이나 xterm 창에서 프롬프트를 제공해주는 의 별도 인스턴스입니다. 명령줄 프롬프트에서 명령어들이 해석되는 것과 마찬가지로, 스크립트도 명령어 목록을 배치 처리합니다. 실행 중인 각 쉘 스크립트는 사실상 부모 쉘의 하위 프로세스(자식 프로세스)입니다.

쉘 스크립트 자체가 하위 프로세스들을 실행할 수 있습니다. 이러한 서브셸들은 스크립트가 병렬 처리를 할 수 있게 해주며, 결과적으로 여러 하위 작업을 동시에 실행하게 합니다.

#!/bin/bash
# subshell-test.sh

(
# 괄호 안쪽, 즉 서브셸 내부...
while [ 1 ]   # 무한 루프.
do
  echo "Subshell running . . ."
done
)

#  이 스크립트는 영원히 실행되거나,
#+ 적어도 Ctl-C로 종료될 때까지 실행됩니다.

exit $?  # 스크립트 종료 (하지만 여기에 도달하지는 않음).

이제 스크립트를 실행하세요: sh subshell-test.sh

그리고 스크립트가 실행 중일 때, 다른 xterm에서 다음을 실행하세요: ps -ef | grep subshell-test.sh

UID       PID   PPID  C STIME TTY      TIME     CMD
500       2698  2502  0 14:26 pts/4    00:00:00 sh subshell-test.sh
500       2699  2698 21 14:26 pts/4    00:00:24 sh subshell-test.sh

          ^^^^

분석: PID 2698, 즉 스크립트가 PID 2699인 서브셸을 실행시켰습니다.

참고: "UID ..." 줄은 "grep" 명령에 의해 걸러지겠지만, 설명을 위해 여기에 표시했습니다.

일반적으로, 스크립트 내의 외부 명령어는 하위 프로세스를 분기(fork)시킵니다.[^1] 반면 Bash 내장 명령어는 그렇지 않습니다. 이러한 이유로 내장 명령어는 해당 외부 명령어에 비해 더 빠르게 실행되며 더 적은 시스템 자원을 사용합니다.

괄호 안의 명령어 목록

  • ( command1; command2; command3; ... ) 괄호 사이에 포함된 명령어 목록은 서브셸로 실행됩니다.

서브셸 내의 변수는 서브셸 코드 블록 외부에서 보이지 않습니다. 이 변수들은 부모 프로세스나 서브셸을 실행시킨 쉘에서 접근할 수 없습니다. 이는 사실상 자식 프로세스지역적인(local) 변수입니다.

예제 21-1. 서브셸 내의 변수 범위(Scope)

#!/bin/bash
# subshell.sh

echo

echo "We are outside the subshell."
echo "Subshell level OUTSIDE subshell = $BASH_SUBSHELL"
# Bash 버전 3은 새로운 $BASH_SUBSHELL 변수를 추가했습니다.
echo; echo

outer_variable=Outer
global_variable=
#  서브셸 변수 값을 "저장"하기 위한
#+ 전역 변수를 정의합니다.

(
echo "We are inside the subshell."
echo "Subshell level INSIDE subshell = $BASH_SUBSHELL"
inner_variable=Inner

echo "From inside subshell, \"inner_variable\" = $inner_variable"
echo "From inside subshell, \"outer\" = $outer_variable"

global_variable="$inner_variable"   #  이렇게 하면 서브셸 변수를
                                    #+ "내보내는(exporting)" 것이 가능할까?
)

echo; echo
echo "We are outside the subshell."
echo "Subshell level OUTSIDE subshell = $BASH_SUBSHELL"
echo

if [ -z "$inner_variable" ]
then
  echo "inner_variable undefined in main body of shell"
else
  echo "inner_variable defined in main body of shell"
fi

echo "From main body of shell, \"inner_variable\" = $inner_variable"
#  $inner_variable은 비어 있는 것처럼 표시됩니다(초기화되지 않음).
#+ 서브셸 내에서 정의된 변수는 "지역 변수"이기 때문입니다.
#  이에 대한 해결책이 있을까요?
echo "global_variable = "$global_variable""  # 이것은 왜 동작하지 않을까요?

echo

# =======================================================================

# 추가적으로 ...

echo "-----------------"; echo

var=41                                                 # 전역 변수.

( let "var+=1"; echo "\$var INSIDE subshell = $var" )  # 42

echo "\$var OUTSIDE subshell = $var"                   # 41
#  서브셸 내부에서의 변수 연산은, 심지어 전역 변수에 대해서도
#+ 서브셸 외부의 변수 값에 영향을 미치지 않습니다!


exit 0

#  질문:
#  --------
#  서브셸을 빠져나온 후,
#+ 서브셸 변수를 수정하거나 접근하기 위해
#+ 동일한 서브셸로 다시 들어갈 수 있는 방법이 있을까요?

$BASHPID예제 34-2도 참조하세요.

정의: 변수의 범위(scope) 는 변수가 의미를 갖고, 그 을 참조할 수 있는 문맥(context)입니다. 예를 들어, 지역 변수의 범위는 그 변수가 정의된 함수, 코드 블록 또는 서브셸 내부로 한정되는 반면, 전역 변수의 범위는 그 변수가 나타나는 전체 스크립트입니다.

참고: $BASH_SUBSHELL 내부 변수는 서브셸의 중첩 수준을 나타내지만, $SHLVL 변수는 서브셸 내부에서 변화가 없습니다.

echo " \$BASH_SUBSHELL outside subshell       = $BASH_SUBSHELL"           # 0
  ( echo " \$BASH_SUBSHELL inside subshell        = $BASH_SUBSHELL" )     # 1
  ( ( echo " \$BASH_SUBSHELL inside nested subshell = $BASH_SUBSHELL" ) ) # 2
# ^ ^                           *** 중첩됨 ***                        ^ ^

echo

echo " \$SHLVL outside subshell = $SHLVL"       # 3
( echo " \$SHLVL inside subshell  = $SHLVL" )   # 3 (변화 없음!)

서브셸에서 변경된 디렉토리는 부모 쉘에 반영되지 않습니다.

예제 21-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' 돌아갈 필요가 없습니다.
#+ 왜냐하면 'cd $home'은 서브셸 내에서 이루어지기 때문입니다.

exit 0

서브셸은 명령어 그룹을 위한 "전용 환경"을 설정하는 데 사용될 수 있습니다.

COMMAND1
COMMAND2
COMMAND3
(
  IFS=:
  PATH=/bin
  unset TERMINFO
  set -C
  shift 5
  COMMAND4
  COMMAND5
  exit 3 # 서브셸만 종료됩니다!
)
# 부모 쉘은 영향을 받지 않았으며, 환경은 보존됩니다.
COMMAND6
COMMAND7

여기서 볼 수 있듯이, exit 명령어는 그것이 실행 중인 서브셸만 종료시키며, 부모 쉘이나 스크립트를 종료시키지 않습니다.

이러한 "전용 환경"의 한 응용은 변수가 정의되었는지 테스트하는 것입니다.

if (set -u; : $variable) 2> /dev/null
then
  echo "Variable is set."
fi     #  변수가 현재 스크립트에서 설정되었거나,
       #+ 내부 Bash 변수이거나,
       #+ 환경에 존재하는 경우(export 되었을 경우)입니다.

# 다음과 같이 작성할 수도 있습니다: [[ ${variable-x} != x || ${variable-y} != y ]]
# 또는                    [[ ${variable-x} != x$variable ]]
# 또는                    [[ ${variable+x} = x ]]
# 또는                    [[ ${variable-x} != x ]]

또 다른 응용은 잠금 파일(lock file)을 확인하는 것입니다:

if (set -C; : > lock_file) 2> /dev/null
then
  :   # lock_file이 존재하지 않음: 해당 스크립트를 실행 중인 사용자가 없음
else
  echo "Another user is already running that script."
exit 65
fi

#  Stéphane Chazelas의 코드 조각,
#+ Paulo Marcel Coelho Aragao가 수정함.

프로세스들은 서로 다른 서브셸 내에서 병렬로 실행될 수 있습니다. 이를 통해 복잡한 작업을 하위 구성 요소로 분할하여 동시에 처리할 수 있습니다.

예제 21-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; . . . commandN; }

var1=23
echo "$var1"   # 23

{ var1=76; }
echo "$var1"   # 76

[^1] exec와 함께 호출된 외부 명령어는 새로운 프로세스를 분기시키지 않습니다. 결과적으로, 서브셸을 실행시키지 않습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment