Bash 스크립트의 동작에 영향을 미치는 변수
Bash 실행 파일의 경로로, 보통은 /bin/bash임
스크립트가 실행될 때 어디에서 bash 시작 파일을 읽을 것인지를 나타내는 환경 변수
원소 갯수가 6개인 배열로, 현재 설치된 Bash 버전에 대한 정보를 담고 있습니다. 다음에 설명할 $BASH_VERSION 과 비슷하지만 좀 더 자세한 정보를 담고 있습니다.
# Bash 버전 정보: for n in 0 1 2 3 4 5 do echo "BASH_VERSINFO[$n] = ${BASH_VERSINFO[$n]}" done # BASH_VERSINFO[0] = 2 # Major version no. # BASH_VERSINFO[1] = 04 # Minor version no. # BASH_VERSINFO[2] = 21 # Patch level. # BASH_VERSINFO[3] = 1 # Build version. # BASH_VERSINFO[4] = release # Release status. # BASH_VERSINFO[5] = i386-redhat-linux-gnu # Architecture # ($MACHTYPE 과 동일). |
시스템에 설치된 Bash 버전
bash$ echo $BASH_VERSION 2.04.12(1)-release |
tcsh% echo $BASH_VERSION BASH_VERSION: Undefined variable. |
어떤 쉘로 동작중인지 알아보려고 할 때 $BASH_VERSION 을 확인해 보는 것은 아주 좋은 방법입니다. 왜냐하면, $SHELL 로는 충분한 정보를 얻을 수 없기 때문입니다.
디렉토리 스택의 내용(pushd와 popd의 영향을 받음)
이 내장 변수는 dirs 명령어와 짝을 이룹니다.
스크립트가 부르는 에디터로서, 보통은 vi나 emacs입니다.
"유효" 사용자 아이디 값
su에 의해 쓰일 현재 사용자의 유효 아이디 값.
경고 |
$EUID는 $UID와 반드시 같지 않습니다. |
현재 함수의 이름
xyz23 () { echo "$FUNCNAME now executing." # xyz23 now executing. } xyz23 echo "FUNCNAME = $FUNCNAME" # FUNCNAME = # 함수 밖에서는 널 값을 갖습니다. |
globbing 시 포함되지 않을 파일명 패턴들의 목록.
현재 사용자가 속해 있는 그룹
/etc/passwd에 적혀 있는 현재 사용자의 그룹 아이디 값을 보여줍니다.
사용자의 홈 디렉토리로, 보통은 /home/username (예 9-10 참고)
hostname 명령어는 부팅시 init 스크립트에서 시스템 이름을 설정해 줍니다. 하지만 gethostname() 함수로 bash 내부 변수인 $HOSTNAME을 설정해 줄 수도 있습니다. 예 9-10을 참고하세요.
host type
$MACHTYPE과 마찬가지로 시스템 하드웨어를 알려줍니다.
bash$ echo $HOSTTYPE i686 |
입력 필드 구분자
디폴트는 공백문자(빈칸, 탭, 뉴라인)지만 콤마로 구분된 데이타 파일을 파싱하려는 경우처럼 변경이 가능합니다. $*는 $IFS의 첫번째 문자를 사용하는 것에 주의하세요. 예 6-1 참고.
bash$ echo $IFS | cat -vte $ bash$ bash -c 'set w x y z; IFS=":-;"; echo "$*"' w:x:y:z |
(S. C. 에게 정확한 설명과 예제에 감사.)
EOF 무시: 로그 아웃하기 전에 몇 개의 end-of-files (control-D)를 무시할 것인지.
주로 .bashrc 나 /etc/profile 에서 세트되는 이 변수는 파일명 확장이나 패턴 매칭시의 대조(collation) 순서를 제어합니다. 이 값이 잘못 처리되면 파일명 globbing시 원치 않는 결과를 가져 올 수 있습니다.
참고: Bash 2.05 이후로, 파일명 globbing 은 대괄호에 들어 있는 문자 범위에 나타나는 대소문자를 더 이상 구분하지 않습니다. 예를 들어, ls [A-M]* 은 File2.txt와 file1.txt 모두와 일치될 것입니다. 자신만의 대괄호 매칭 동작을 원래대로 바꾸려면 /etc/profile이나 ~/.bashrc등에 export LC_COLLATE=C 라고 해서 LC_COLLATE을 C로 세트해 주면 됩니다.
이 내부 변수는 globbing 과 패터 매칭의 문자 해석을 제어합니다.
쉘 스크립트에서 이 변수가 들어 있는 줄의 줄번호를 나타내는데, 스크립트에서 쓰일 때만 의미가 있고, 주로 디버깅에 쓰입니다.
last_cmd_arg=$_ # 저장. echo "$LINENO 번째 줄, 변수 \"v1\" = $v1" echo "처리된 마지막 명령어 인자 = $last_cmd_arg" |
머신 종류
시스템 하드웨어를 구분해 줍니다.
bash$ echo $MACHTYPE i686-debian-linux-gnu |
바로 전 작업 디렉토리 ("OLD-print-working-directory")
운영 체제 종류
bash$ echo $OSTYPE linux-gnu |
실행 파일의 경로, 보통은 /usr/bin/, /usr/X11R6/bin/, /usr/local/bin, 등등.
명령어가 주어지면 쉘은 실행 파일들을 위한 경로에 들어있는 디렉토리들에 대해서 자동으로 해쉬 테이블 탐색을 수행합니다. 경로에는 환경 변수인 $PATH에 디렉토리들이 콜론으로 구분되어 들어 있습니다. 보통 $PATH 정의는 /etc/profile이나 ~/.bashrc에 들어 있습니다(27장 참고).
bash$ echo $PATH /bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin:/sbin:/usr/sbin |
PATH=${PATH}:/opt/bin 라고 하면 /opt/bin을 현재 경로에 추가 시킵니다. 이렇게 하면 경로에 임시로 디렉토리를 적절하게 추가할 수 있습니다. 스크립트가 끝나면 원래 $PATH 로 복구됩니다(스크립트같은 자식 프로세스는 부모 프로세스인 쉘의 환경 변수를 바꿀 수 없습니다).
참고: 보통은 현재 "작업 디렉토리"를 나타내는 ./은 보안상의 이유로 $PATH에서 제외시킵니다.
마지막으로 실행된 파이프의 종료 상태. 아주 재미있는 것은, 이 결과와 마지막으로 실행된 명령어의 종료 상태가 똑같지는 않다는 것입니다.
bash$ echo $PIPESTATUS 0 bash$ ls -al | bogus_command bash: bogus_command: command not found bash$ echo $PIPESTATUS 141 bash$ ls -al | bogus_command bash: bogus_command: command not found bash$ echo $? 127 |
어떤 프로세스의 부모 프로세스의 프로세스 아이디(pid)를 $PPID이라고 합니다. [1]
이것들을 pidof 명령어와 비교해 보세요.
명령어줄에서 볼 수 있는 메인 프롬프트.
2차 프롬프트로, 추가적인 입력이 필요할 때 ">" 로 표시됩니다.
4차 프롬프트로, 스크립트에 -x 옵션이 걸려서 실행될 때 스크립트의 매 줄마다 "+"로 표시됩니다.
작업 디렉토리(현재 있는 디렉토리)
내장 명령인 pwd와 비슷합니다.
#!/bin/bash E_WRONG_DIRECTORY=73 clear # 화면 정리. TargetDirectory=/home/bozo/projects/GreatAmericanNovel cd $TargetDirectory echo "$TargetDirectory 디렉토리의 오래된 파일을 지웁니다." if [ "$PWD" != "$TargetDirectory" ] then # 실수로 다른 디렉토리를 지우지 않게 합니다. echo "지울 디렉토리가 아닙니다!" echo "$TargetDirectory 디렉토리가 아니라 $PWD 디렉토리입니다," echo "취소합니다!" exit $E_WRONG_DIRECTORY fi rm -rf * rm .[A-Za-z0-9]* # 도트 파일 삭제. # 이름이 여러개의 점으로 시작하는 파일도 지우려면 이렇게 하세요. rm -f .[^.]* ..?* # (shopt -s dotglob; rm -f *) 라고 해도 됩니다. # 지적해 줘서 고마워요. S.C. # 파일이름에는 "/"를 제외하고 0 - 255 사이의 모든 문자가 들어갈 수 있습니다. # 이상한 문자로 시작하는 파일을 지우는 것은 연습문제로 남겨 놓습니다. # 필요하다면 다른 작업을 하세요. echo echo "끝." echo "$TargetDirectory 디렉토리의 오래된 파일을 모두 삭제했습니다." echo exit 0 |
read에 변수가 안 주어졌을 때 저장되는 기본값이고, select 메뉴에서는 변수의 값이 아니라 선택한 숫자가 저장됩니다.
#!/bin/bash echo echo -n "제일 좋아하는 야채가 뭐에요? " read echo "제일 좋아하는 야채가 $REPLY 군요." # REPLY 는 가장 최근의 "read"가 변수 없이 주어졌을 때 그 값을 담고 있습니다. echo echo -n "제일 좋아하는 과일은요? " read fruit echo "당신이 제일 좋아하는 과일은 $fruit 지만," echo "\$REPLY 의 값은 여전히 $REPLY 네요." # $fruit 변수가 "read"의 값을 가져가 버렸기 때문에 $REPLY 는 여전히 # 앞에서 설정된 값을 갖고 있습니다. echo exit 0 |
스크립트가 얼마나 돌았는지를 나타내는 초 단위 시간.
#!/bin/bash ENDLESS_LOOP=1 INTERVAL=1 echo echo "스크립트를 끝내려면 Control-C 를 누르세요." echo while [ $ENDLESS_LOOP ] do if [ "$SECONDS" -eq 1 ] then units=second else units=seconds fi echo "이 스크립트는 $SECONDS $units 동안 돌고 있습니다." sleep $INTERVAL done exit 0 |
현재 켜 있는 쉘 옵션들의 목록으로서 읽기 전용 변수
쉘 레벨로 Bash 가 얼마나 깊이 중첩되어 있는지를 나타냄. 만약에 명령어줄에서 $SHLVL 이 1 이었다면 스크립트에서는 2 가 됩니다.
$TMOUT 환경 변수를 0 이 아닌 값 time으로 설정해 놓으면 그 시간이 지난 다음에는 로그아웃이 됩니다.
참고: 불행하게도 이 변수는 콘솔상의 쉘 프롬프트나 엑스텀(한텀)에서만 동작합니다. 예를 들어, read 문에서 $TMOUT을 같이 쓰고 싶겠지만 실제로는 제대로 동작하지 않습니다.(ksh 버전에서는 read의 타임아웃이 제대로 동작한다고 보고된 바 있습니다).
타임 아웃을 쉘 스크립트에서 구현할 수는 있으나 그렇게 효과적이지 않습니다. 한가지 가능한 방법은, 타임 아웃이 났을 때 타이밍 루프가 스크립트에게 시그널을 보내도록 세팅해야 하고 타이밍 루프가 발생시킨 (예 30-4 참고) 인터럽트를 처리할 시그널 처리 루틴도 만들어야 합니다, 휴~~.
예 9-2. 타임 아웃 처리 입력
#!/bin/bash # timed-input.sh # TMOUT=3 스크립트에서는 쓸모가 없습니다. TIMELIMIT=3 # 여기서는 3초, 다른 값으로 세트할 수 있습니다. PrintAnswer() { if [ "$answer" = TIMEOUT ] then echo $answer else # 두 번의 인스턴스를 구분하기 위해서. echo "제일 좋아하는 야채는 $answer 이군요." kill $! # 백그라운드에서 도는 TimerOn 함수가 더 이상 필요없기 때문에 kill 시킴. # $! 는 백그라운드에서 돌고 있는 가장 최근 작업의 PID 입니다. fi } TimerOn() { sleep $TIMELIMIT && kill -s 14 $$ & # 3초를 기다리고 알람 시그널(sigalarm)을 스크립트에 보냄. } Int14Vector() { answer="TIMEOUT" PrintAnswer exit 14 } trap Int14Vector 14 # 타이머 인터럽트(14)는 우리 의도대로 타임아웃을 처리함. echo "제일 좋아하는 야채가 뭐죠? " TimerOn read answer PrintAnswer # 이는 분명히 타임아웃 입력에 대한 미봉책입니다만, # Bash 로 할 수 있는 최선을 다한 것입니다. # (독자들에게 도전: 더 나은 해결책을 제시해 보세요.) # 더 우아한 다른 것이 필요하다면 C 나 C++ 의 'alarm'이나 'setitimer' 같은 # 적당한 라이브러리 함수를 써서 구현하기 바랍니다. exit 0 |
stty를 쓴 다른 방법.
예 9-3. 타임 아웃 처리 입력, 한 번 더
#!/bin/bash # timeout.sh # Stephane Chazelas 작성. # 이 문서의 저자가 수정. INTERVAL=5 # 타임아웃 인터벌 timedout_read() { timeout=$1 varname=$2 old_tty_settings=`stty -g` stty -icanon min 0 time ${timeout}0 eval read $varname # 아니면 그냥 read $varname stty "$old_tty_settings" # "stty" 맨 페이지 참조. } echo; echo -n "이름이 뭐죠? 빨리 대답해요! " timedout_read $INTERVAL your_name # 모든 터미널 타입에서 동작하지 않을 수도 있습니다. # 최대 타임아웃은 어떤 터미널이냐에 달려있습니다. # (보통은 25.5 초). echo if [ ! -z "$your_name" ] # 타임아웃 전에 입력이 있다면... then echo "아하, 이름이 $your_name 군요." else echo "타임아웃." fi echo # 이 스크립트는 "timed-input.sh" 스크립트와 약간 다르게 동작하는데, # 키가 눌릴 때마다 타임아웃 카운터가 리셋됩니다. exit 0 |
사용자 아이디 값
/etc/passwd에 저장되어 있는 현재 사용자의 사용자 식별 숫자
비록 su에 의해서 임시로 다른 사용자로 인식되더라도 현재 사용자의 실제 아이디를 나타냅니다. $UID는 읽기만 되는 변수로 명령어 줄이나 스크립트에서 변경할 수 없습니다. 그리고, id 내장 명령과 짝을 이룹니다.
예 9-4. 내가 루트인가?
#!/bin/bash # am-i-root.sh: 내가 루트야 아니야? ROOT_UID=0 # 루트 $UID는 0. if [ "$UID" -eq "$ROOT_UID" ] # Will the real "root" please stand up? then echo "루트네요." else echo "그냥 보통 사용자에요.(그래도 당신 어머니는 있는 그대로의 당신을 사랑하신답니다)." fi exit 0 # ============================================================= # # 스크립트가 이미 종료됐기 때문에 밑의 코드는 실행되지 않습니다. # 루트인지 알아내는 다른 방법: ROOTUSER_NAME=root username=`id -nu` if [ "$username" = "$ROOTUSER_NAME" ] then echo "루티 투트 투트(Rooty, toot, toot), 당신은 루트 사용자군요." else echo "그냥 보통 사람이군요." fi exit 0 |
예 2-2 참고.
참고: $ENV, $LOGNAME, $MAIL, $TERM, $USER, $USERNAME은 bash 내장 명령이 아닙니다. 하지만 종종 bash 시스템 구동 파일에서 환경 변수로 설정이 됩니다. 사용자의 로긴 쉘을 나타내는 $SHELL은 /etc/passwd를 참고해 설정되거나 "init" 스크립트에서 설정되고, 역시 bash 내장명령어가 아닙니다.
tcsh% echo $LOGNAME bozo tcsh% echo $SHELL /bin/tcsh tcsh% echo $TERM rxvt bash$ echo $LOGNAME bozo bash$ echo $SHELL /bin/tcsh bash$ echo $TERM rxvt
위치 매개변수(Positional Parameters)
위치 매개변수로서, 명령어줄에서 스크립트로 넘겨지거나 함수로 넘겨지거나 set 명령어로 강제로 설정됨(예 5-5 과 예 11-10 참고).
한 낱말로 표시되는 위치 매개변수들 모두
$*과 똑같지만 각 매개변수는 쿼우트된 문자열로 취급됩니다. 즉, 해석되거나 확장없이 있는 그대로 넘겨집니다. 그 결과로 각 인자는 각각이 서로 다른 낱말로 구분돼서 표시됩니다.
예 9-5. arglist: $* 과 $@ 로 인자를 나열하기
#!/bin/bash # 이 스크립트를 부를 때 "one two three" 같은 인자를 줘서 부르세요. E_BADARGS=65 if [ ! -n "$1" ] then echo "사용법: `basename $0` argument1 argument2 etc." exit $E_BADARGS fi echo index=1 echo "\"\$*\" 로 인자를 나열하기:" for arg in "$*" # "$*" 를 쿼우트하지 않으면 제대로 동작하지 않습니다. do echo "Arg #$index = $arg" let "index+=1" done # $* 는 모든 인자를 하나의 낱말로 봅니다. echo "전체 인자 목록은 하나의 낱말로 나타납니다." echo index=1 echo "\"\$@\" 로 인자를 나열하기:" for arg in "$@" do echo "Arg #$index = $arg" let "index+=1" done # $@ 는 인자들을 분리된 낱말로 봅니다. echo "전체 인자 목록은 분리된 낱말로 나타납니다." echo exit 0 |
특수 매개변수인 $@에는 쉘 스크립트로 들어오는 입력을 필터링하는 툴로서 쓰입니다. cat "$@"은 자신의 매개변수를 표준입력이나 파일에서 받아 들일 수 있습니다. 예 12-17을 참고하세요.
경고 |
$*과 $@은 가끔 $IFS의 설정값에 따라 일관성이 없고 이상한 동작을 합니다. |
예 9-6. 일관성 없는 $*과 $@의 동작
#!/bin/bash # 쿼우트 여부에 따라 이상하게 동작하는 # Bash 내부 변수 "$*"와 "$@". # 낱말 조각남과 라인피드가 일관성 없이 처리됩니다. # 이 예제 스크립트는 Stephane Chazelas 가 제공하고, # 본 문서의 저자가 약간 수정했습니다. set -- "첫번째 인자" "두번째" "세번째:인자" "" "네번째: :인자" # 스크립트의 인자를 $1, $2 등으로 세팅. echo echo 'IFS 는 그대로, "$*"' c=0 for i in "$*" # 쿼우트 do echo "$((c+=1)): [$i]" # 매 인스턴스마다 똑같음. # 인자 에코. done echo --- echo 'IFS 는 그대로, $*' c=0 for i in $* # 쿼우트 안 함 do echo "$((c+=1)): [$i]" done echo --- echo 'IFS 는 그대로, "$@"' c=0 for i in "$@" do echo "$((c+=1)): [$i]" done echo --- echo 'IFS 는 그대로, $@' c=0 for i in $@ do echo "$((c+=1)): [$i]" done echo --- IFS=: echo 'IFS=":", "$*"' c=0 for i in "$*" do echo "$((c+=1)): [$i]" done echo --- echo 'IFS=":", $*' c=0 for i in $* do echo "$((c+=1)): [$i]" done echo --- var=$* echo 'IFS=":", "$var" (var=$*)' c=0 for i in "$var" do echo "$((c+=1)): [$i]" done echo --- echo 'IFS=":", $var (var=$*)' c=0 for i in $var do echo "$((c+=1)): [$i]" done echo --- var="$*" echo 'IFS=":", $var (var="$*")' c=0 for i in $var do echo "$((c+=1)): [$i]" done echo --- echo 'IFS=":", "$var" (var="$*")' c=0 for i in "$var" do echo "$((c+=1)): [$i]" done echo --- echo 'IFS=":", "$@"' c=0 for i in "$@" do echo "$((c+=1)): [$i]" done echo --- echo 'IFS=":", $@' c=0 for i in $@ do echo "$((c+=1)): [$i]" done echo --- var=$@ echo 'IFS=":", $var (var=$@)' c=0 for i in $var do echo "$((c+=1)): [$i]" done echo --- echo 'IFS=":", "$var" (var=$@)' c=0 for i in "$var" do echo "$((c+=1)): [$i]" done echo --- var="$@" echo 'IFS=":", "$var" (var="$@")' c=0 for i in "$var" do echo "$((c+=1)): [$i]" done echo --- echo 'IFS=":", $var (var="$@")' c=0 for i in $var do echo "$((c+=1)): [$i]" done echo # 이 스크립트를 ksh 이나 zsh -y 로 실행해 보세요. exit 0 |
참고: $@과 $*는 큰따옴표로 쿼우트 됐을 때만 달라집니다.
예 9-7. $IFS 가 비어 있을 때 $*와 $@
#!/bin/bash # $IFS 가 빈 상태로 세트됐다면, # "$*" 와 "$@" 는 위치 매개변수를 우리가 생각하는대로 에코하지 않습니다. mecho () # 위치 매개변수 에코. { echo "$1,$2,$3"; } IFS="" # 빈 상태로 세트. set a b c # 위치 매개변수. mecho "$*" # abc,, mecho $* # a,b,c mecho $@ # a,b,c mecho "$@" # a,b,c # $IFS 가 비어 있을 경우에 $* 와 $@ 의 동작은 # Bash 나 sh 의 버전에 따라 달라지기 때문에 # 스크립트에서 이 "기능"에 쓰는 것은 권장하지 않습니다. # Thanks, S.C. exit 0 |
다른 특수 매개변수
스크립트로 넘겨진 플래그들
경고 |
원래 ksh에서 쓰던 것을 bash에서 채택한 것인데 불행히도 잘 동작하지 않는 것 같습니다. 스크립트 자신이 대화형인지 아닌지 스스로 확인하려고 하는 경우에나 쓸만합니다. |
백그라운드로 돌고 있는 가장 최근 작업의 PID (process id)
바로 이전에 실행된 명령어의 제일 마지막 인자로 설정되는 특수 변수.
스크립트 자신의 프로세스 아이디로 보통 임시 파일 이름을 만들 때 사용합니다(예 A-8와 예 30-5, 예 12-23 참고).
[1] | 현재 돌고 있는 스크립트의 pid 는 $$ 입니다. |
[2] | "인자"(argument)나 "매개변수" (parameter)는 흔히 서로 바꿔서 쓰기 때문에 이 문서에서는 스크립트나 함수로 넘겨지는 변수를 나타내는 같은 의미로 쓰입니다. |