원본: https://tldp.org/LDP/abs/html/subshells.html
쉘 스크립트를 실행하면 새로운 프로세스인 서브셸이 실행됩니다.
정의: 서브셸은 쉘(또는 쉘 스크립트)에 의해 실행된 자식 프로세스입니다.
서브셸은 명령어 프로세서, 즉 콘솔이나 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) 변수입니다.
#!/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
# 질문:
# --------
# 서브셸을 빠져나온 후,
#+ 서브셸 변수를 수정하거나 접근하기 위해
#+ 동일한 서브셸로 다시 들어갈 수 있는 방법이 있을까요?정의: 변수의 범위(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 (변화 없음!)
서브셸에서 변경된 디렉토리는 부모 쉘에 반영되지 않습니다.
#!/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가 수정함.프로세스들은 서로 다른 서브셸 내에서 병렬로 실행될 수 있습니다. 이를 통해 복잡한 작업을 하위 구성 요소로 분할하여 동시에 처리할 수 있습니다.
(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와 함께 호출된 외부 명령어는 새로운 프로세스를 분기시키지 않습니다. 결과적으로, 서브셸을 실행시키지 않습니다.