2012-09-13

FreeBSD]Bash 스크립팅 가이드2



3장. 종료와 종료 상태(Exit and Exit Status)

 
사람들은 본쉘의 어두운 면을 모두 사용한다.
 Chet Ramey
exit 명령어는 C 프로그램에서처럼 스크립트를 끝낼 때 씁니다. 또한, 스크립트의 부모 프로세스에게 어떤 값을 돌려 줄 수도 있습니다.
모든 명령어는 종료 상태( exit status (가끔은 리턴 상태( return status )라고도 하는)를 리턴합니다. 명령어가 성공시에는 0을 리턴하고 실패시에는 에러 코드로 해석될 수 있는 non-zero를 리턴합니다. 예외가 있기는 하지만, 유닉스 관례를 잘 따르는 명령어, 프로그램, 유틸리티는 성공했을 때 0을 리턴합니다.
비슷하게, 스크립트의 함수나 스크립트 자신도 종료 상태를 리턴합니다. 스크립트 함수나 스크립트에서 가장 마지막에 실행된 명령어가 종료 상태를 결정합니다. 스크립트에서 exit nnn 이라고 하면 nnn이라는 종료 상태를 쉘에게 전달해 줍니다(nnn은 0에서255 사이의 십진수여야 합니다).
참고: 매개변수 없이 그냥 exit로 끝났을 경우에는, 마지막에 실행된 명령어(exit 자신은 빼고)의 종료 상태가 스크립트의 종료 상태가 됩니다.
$? 는 제일 마지막 명령어의 종료 상태를 보여줍니다. 함수가 리턴한 다음에 $?라고 하면 함수의 마지막 명령어의 종료 상태를 알려줍니다. bash에서는 이렇게 해서 함수의 "반환값"을 돌려 줍니다. 스크립트가 종료한 다음에는 명령어줄에서 $?로 스크립트 마지막 명령어의 종료 상태를 알 수가 있는데 관습적으로 0은 성공을 나타내고 1에서 255까지의 숫자는 에러를 나타냅니다.
예 3-1. 종료/종료 상태
          #!/bin/bash echo hello echo $? # 명령어가 성공했기 때문에 종료 상태 0이 리턴됨. lskdf # 알수없는 명령어. echo $? # 0이 아닌 종료 상태가 리턴됨. echo exit 113 # 쉘에게 113을 리턴함. # 확인해 보려면 이 스크립트가 종료된 다음에 "echo $?"라고 쳐 보세요. # 관습적으로 'exit 0'은 성공을 의미합니다. # 0이 아닌 값은 에러나 예외상황을 나타냅니다.
          
$? 는 스크립트에서 실행시키 명령어의 결과를 확인하는데 특별히 유용하게 쓰입니다(예 12-8 와 예 12-13 참고).
참고: 논리적 "부정" 한정어(qualifier)인 ! 는 테스트나 명령어의 결과를 반대로 바꿔서 종료 상태에 영향을 미칩니다.
예 3-2. !으로 조건을 부정하기
          true # 쉘 내장명령어인 "true". echo "\"true\"의 종료 상태 = $?" # 0 ! true echo "\"! true\"의 종료 상태 = $?" # 1 # 조심할 점은 "!"을 쓸 때, 빈 칸이 있어야 된다는 것입니다. # 그냥 !true 라고 쓰면 "command not found" 에러가 납니다. # Thanks, S.C.
          
경고
몇몇 종료 상태 코드들은 예약돼 있기 때문에 사용자가 스크립트에서 마음대로 쓰면 안 됩니다.

4장. 특수 문자

쉘 스크립트에서 쓰이는 특수 문자들
#
주석. #으로 시작하는 줄(#!만 빼고)은 주석입니다.
          # 이 줄은 주석입니다.
          
명령어 끝에 주석이 나올 수도 있습니다.
          echo "뒤에 주석이 나옵니다." # 주석이 여기에.
          
줄 첫부분에 나오는 공백문자뒤에도 주석을 쓸 수 있습니다.
           # 이 주석 앞에 탭이 있습니다.
          
경고
한 줄에서 주석뒤에 명령어가 올 수는 없습니다. 주석과 "실제 코드"를 구분할 방법이 없기 때문에 다른 명령어는 새로운 줄에 쓰세요.
참고: 당연한 이야기지만, echo 문에서 이스케이프된 #은 주석의 시작을 나타내지 않습니다. 비슷하게 몇몇 매개변수 치환이나 산술 상수 확장에 나오는 #도 주석을 나타내지 않습니다.
          echo "이 # 은 주석의 시작이 아닙니다." echo '이 # 은 주석의 시작이 아닙니다.' echo 이 \# 은 주석의 시작이 아닙니다. echo 이 # 은 주석의 시작을 나타냅니다. echo ${PATH#*:} # 매개변수 치환으로, 주석이 아니죠. echo $(( 2#101011 )) # 진법 변환, 주석이 아닙니다. # Thanks, S.C.
          
표 준 쿼우팅(quoting)과 이스케이프(escape) 문자들인 (" ' \) 들은 # 을 이스케이프 시킬 수 있습니다.
몇몇 패턴 매칭 연산자도 #을 사용합니다.
;
명령어 구분자. [세미콜론] 두 개 이상의 명령어를 한 줄에서 같이 쓸 수 있게 해줍니다.
          echo hello; echo there
          
";"는 가끔 이스케이프 시킬 필요가 있습니다.
;;
case 옵션 종료자. [이중 세미콜론]
          case "$variable" in abc) echo "$variable = abc" ;; xyz) echo "$variable = xyz" ;; esac
          
.
"점"(dot) 명령어. [마침표] source 명령어와 동일합니다(예 11-14 참고). 이 명령어는 bash 내장 명령(builtin)입니다.
"점"(dot)이 정규 표현식(reqular expression)으로 해석될 때는, 한 개의 문자와 일치됩니다.
또다른 문맥에서는 그냥 ls 라고 쳤을 때, 보이지 않는 "숨김" 파일을 나타내는 파일명 접두어로도 쓰입니다.
          bash$ touch .hidden-file bash$ ls -l total 10 -rw-r--r-- 1 bozo 4034 Jul 18 22:04 data1.addressbook -rw-r--r-- 1 bozo 4602 May 25 13:58 data1.addressbook.bak -rw-r--r-- 1 bozo 877 Dec 17 2000 employment.addressbook bash$ ls -al total 14 drwxrwxr-x 2 bozo bozo 1024 Aug 29 20:54 ./ drwx------ 52 bozo bozo 3072 Aug 29 20:51 ../ -rw-r--r-- 1 bozo bozo 4034 Jul 18 22:04 data1.addressbook -rw-r--r-- 1 bozo bozo 4602 May 25 13:58 data1.addressbook.bak -rw-r--r-- 1 bozo bozo 877 Dec 17 2000 employment.addressbook -rw-rw-r-- 1 bozo bozo 0 Aug 29 20:54 .hidden-file
          
"
부분 쿼우팅(partial quoting). [이중 쿼우트] "문자열" 이라고 하면 쉘이 문자열에 들어 있는 거의 대부분의 특수 문자를 해석하지 못하도록 막아줍니다. 6장을 참고하세요.
'
완전 쿼우팅(full quoting). [단일 쿼우트] '문자열' 이라고 하면 쉘이 문자열에 들어 있는 모든 특수 문자를 해석하지 못하도록 막아줍니다. "보다 더 강한 형태의 쿼우팅입니다. 6장을 참고하세요.
,
콤마 연산자. 콤마 연산자 는 연속적인 산술 연산을 하려고 할 때 쓰입니다. 모든 계산이 이루어진뒤, 마지막에 계산된 결과만 리턴됩니다.
          let "t2 = ((a = 9, 15 / 3))" # "a"를 세트하고 "t2"를 계산.
          
\
이스케이프(escape). [역슬래쉬] \X라 고 하면 X 문자를 "이스케이프" 시키고, 'X' 라고 "쿼우팅" 시키는 것과 동일한 효과를 갖습니다. \는 "나 '이 문자 그대로 해석되도록 쿼우트 할 때 쓰일 수도 있습니다.
이스케이프된 문자들에 대한 설명이 6장에 자세하게 되어 있습니다.
/
파일명 경로 구분자. [슬래쉬] 파일명에 등장하는 각 요소들을 구분해 줍니다(/home/bozo/projects/Makefile 처럼).
나누기 산술 연산자도 됩니다.
`
명령어 치환(command substitution). [백틱(backticks)] `명령어` 라고 하면 명령어의 결과를 변수값으로 설정할 수가 있습니다. 다른 말로 backticks나 역쿼우트(backquote)라고도 합니다.
:
널 명령어(null command). 쉘의 "NOP"(no op, 아무 동작도 않함)에 해당합니다. 쉘 내장 명령인 true의 동의어라고도 볼 수 있습니다. 주의할 점은 :은 bash 내장 명령이기 때문에 종료 상태는 0이라는 것입니다.
          : echo $? # 0
          
무한 루프:
          while : do 첫번째 연산 두번째 연산 ... n번째 연산 done # 이는 다음과 같습니다: # while true # do # ... # done
          
if/then 테스트 문의 Placeholder:
          if condition then : # 아무것도 안 하고 계속 진행 else 어떤 작업 fi
          
이진 연산의 placeholder 제공, 예 8-1 와 디폴트 매개변수 참고.
          : ${username=`whoami`} # "username"이 명령어나 내장 명령어가 아닌 경우에 # ${username=`whoami`} 에 : 없이 쓰면 에러가 납니다.
          
here document가 나올 곳의 placeholder를 제공. 예 17-8 참고.
매개변수 치환을 써서 변수의 문자열 평가(예 9-10 참고).
          : ${HOSTNAME?} ${USER?} ${MAIL?} # 필수적인 환경변수중 하나라도 세트가 안 돼 있다면 에러를 출력.
          
재지향 연산자인 >과 같이 써서 특정 파일의 퍼미션 변경 없이 크기를 0으로 만들어 줍니다. 파일이 없었다면 새로 만들어 냅니다.
          : > data.xxx # "data.xxx"은 이제 빈 파일입니다. # cat /dev/null >data.xxx 라고 한 것과 동일하지만 # ":"가 내장 명령어이기 때문에 새 프로세스를 포크(fork) 시키지 않습니다.
          
예 12-11 참고.
역시 재지향 연산자인 >>과 같이 쓰면 파일의 억세스/변경 시간을 업데이트 해 줍니다(: >> new_file). 파일이 없었다면 새로 만들어 냅니다. touch와 같습니다.
참고: 보통 파일에만 사용하고 파이프나 심볼릭 링크, 특수 파일에는 사용하지 마세요.
권장하는 방법은 아닙니다만, 주석의 시작을 나타낼 때 쓸 수도 있습니다. 주석에 #을 쓰게 되면 그 줄의 나머지 부분에 대해서 에러 확인을 안 하기 때문에 어떤 문장도 올 수 있지만 :의 경우는 다릅니다.
          : 이 주석은 에러를 발생시킵니다, ( if [ $x -eq 3] ).
          
":"는 또한 /etc/passwd와 $PATH 변수에서 필드 구분자로도 쓰입니다.
          bash$ echo $PATH /usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/sbin:/usr/sbin:/usr/games
          
!
테스트나 종료 상태의 의미를 반대나 부정해 줍니다. ! 연산자는 해당 명령어의 종료 상태를 반대로 해 놓습니다(예 3-2 참고). 또한, 테스트 연산자의 의미도 거꾸로 바꿔 주는데 예를 들어, "equal"= )을 "not-equal" ( != )로 해석하게 해 줍니다. ! 연산자는 bash 키워드입니다.
다른 상황에서는 간접 변수 참조의 의미로도 쓰입니다.
*
와일드 카드. [별표] * 문자는 정규 표현식에서 0개 이상의 문자를 나타내는 것과 동일하게 파일명 확장(globbing)에서 "와일드 카드"처 럼 쓰입니다.
이중 별표, **, 는 수학의 누승(累乘, exponentiation) 연산자입니다.
?
와일드 카드(하나의 문자). [물음표] ? 문자는 확장 정규 표현식에서 한 문자를 나타내는 것과 마찬가지로 글로빙(globbing)에서 파일명 확장을 나타내는 한 문자짜리 "와일드 카드"의 역할을 합니다.
?은 이중 소괄호에서 C 스타일의 삼중 연산자로도 쓰입니다. 예 9-22를 참고하세요.
$
          var1=5 var2=23skidoo echo $var1 # 5 echo $var2 # 23skidoo
          
${}
$*$@
()
명령어 그룹.
          (a=hello; echo $a)
          
중요: 소괄호로 묶인 명령어들은 서브쉘에서 동작합니다.
스크립트의 다른 곳에서는 소괄호 안의 서브쉘에 들어 있는 변수들을 볼 수가 없습니다. 부모 프로세스인 스크립트는 자식 프로세스(서브쉘)에서 만들어진 변수를 읽을 수가 없습니다.
          a=123 ( a=321; ) echo "a = $a" # a = 123 # 소괄호 안의 "a" 는 지역변수처럼 동작합니다.
          
배열 초기화.
          Array=(element1 element2 element3)
          
{xxx,yyy,zzz,...}
중괄호 확장.
          grep Linux file*.{txt,htm*} # "fileA.txt", "file2.txt", "fileR.html", "file-87.htm" 등등의 파일에서 # "Linux"가 들어 있는 것을 모두 찾음
          
명령어는 중괄호안의 콤마로 분리 지정된 파일 스펙에 맞게 동작할 것입니다. [1] 파일명 확장(globbing)은 중괄호 안에서 지정된 파일 스펙에 적용됩니다.
경고
빈 칸은 쿼우트(quote)나 이스케이프(escape)되지 않고 중괄호에서 쓰일 수 없습니다.
echo {file1,file2}\ :{\ A," B",' C'}
file1 : A file1 : B file1 : C file2 : A file2 : B file2 : C
{}
코드 블럭. [중괄호] "인라인 그룹"이라고도 부르는 중괄호 한 쌍은 실제로 익명의 함수를 만들어 냅니다만 보통의 함수와는 달리 코드 블럭 안의 변수들을 스크립트의 다른 곳에서 볼 수가 있습니다.
          bash$ { local a; a=123; } bash: local: can only be used in a function
          
          a=123 { a=321; } echo "a = $a" # a = 321 (코드 블럭에서 설정된 값) # Thanks, S.C.
          
중괄호로 묶인 코드 블럭은 I/O 재지향되거나 재지향을 받을 수 있습니다.
예 4-1. 코드 블럭과 I/O 재지향
          #!/bin/bash # /etc/fstab 읽기 File=/etc/fstab { read line1 read line2 } < $File echo "$File 파일의 첫번째 줄:" echo "$line1" echo echo "$File 파일의 두번째 줄:" echo "$line2" exit 0
          
예 4-2. 코드 블럭의 결과를 파일로 저장하기
          #!/bin/bash # rpm-check.sh # rpm 파일에 대해서 설치가능여부, 설치정보, 설치목록에 대해서 쿼리를 하고, #+ 그 결과를 파일로 저장합니다. # # 이 스크립트는 코드 블럭이 어떻게 쓰이는지 보여줍니다. SUCCESS=0 E_NOARGS=65 if [ -z "$1" ] then echo "사용법: `basename $0` rpm-file" exit $E_NOARGS fi { echo echo "아카이브 정보:" rpm -qpi $1 # 설치정보 쿼리. echo echo "아카이브 목록:" rpm -qpl $1 # 설치목록 쿼리. echo rpm -i --test $1 # 설치가능여부 쿼리. if [ "$?" -eq $SUCCESS ] then echo "$1 는 설치될 수 있습니다." else echo "$1 는 설치될 수 없습니다." fi echo } > "$1.test" # 블럭의 모든 출력을 파일로 재지향. echo "$1.test 파일에 rpm 테스트의 결과가 저장되었습니다." # 여기서 쓰인 옵션에 대한 설명은 맨 페이지를 참고하세요. exit 0
          
참고: 위에서 설명했던 (소괄호)로 묶인 명령어 그룹과는 달리 {중괄호}로 묶인 코드 블럭은 보통은 서브쉘을 띄우지 않습니다.[2]
{} \;
경로명. 주로 find에서 쓰이고, 쉘 내장 명령이 아닙니다.
참고: ";" 는 find 명령어의 -exec 옵션이 여러개 나올 때 끝을 나타내기 때문에 쉘이 해석하는 것을 막기 위해서 이스케이프 시켜줘야 됩니다.
[ ]
테스트.
[ ] 사이의 테스트문. [는 쉘 내장 명령인 test와 동의어로서, 외부 명령어인 /usr/bin/test의 링크가 아닙니다.
[[ ]]
테스트.
[[ ]] 사이의 테스트문(쉘 키워드).
더 자세한 사항은 [[ ... ]]을 참고하세요.
(( ))
정수 확장.
(( ))에 들어 있는 정수 표현식을 확장하고 평가해 줍니다.
더 자세한 설명은 (( ... ))를 참고하세요.
> >& >> <
scriptname >filename 은 scriptname의 결과를 filename으 로 재지향시킵니다. 이 때, fielname이 이미 있다면 덮어 써집니다.
command >&2는 command의 결과를 표준에러로 재지향 시킵니다.
scriptname >>filename은 scriptname의 결과를 filename 으로 덧붙입니다. 이 때, filename이 없다면 새로 만듭니다.
프로세스 치환(process substitution).
(command)>
<(command)
"<" 와 ">" 문자는 다른 문맥에서 문자열 비교 연산자로 동작합니다.
또 다른 문맥에서는 정수 비교 연산자로 동작합니다. 예 12-6를 참고하세요.
<<
here document에서 쓰이는 재지향.
|
파이프. 여러 명령어들을 연결하는 방법으로써, 한 명령어의 출력을 다음 명령어나 쉘에게 전달.
          echo ls -l | sh # "echo ls -l" 의 출력을 쉘에게 전달하는데, #+ 그냥 "ls -l" 라고 한 것과 똑같습니다. cat *.lst | sort | uniq # 모든 ".lst" 파일들을 합친 다음 정렬하고 중복된 줄들을 지웁니다.
          
명령어의 출력이나 명령어 자체를 스크립트로 파이프를 걸 수도 있습니다.
          #!/bin/bash # uppercase.sh : 입력을 대문자로 바꿔줌. tr 'a-z' 'A-Z' # 한 글자짜리 파일 이름이 생기는 걸 막기 위해서 #+ 문자 범위는 꼭 쿼우트 시켜야 합니다. exit 0
          
자, 이제 ls -l의 출력에 파이프를 걸어 이 스크립트로 넘겨 봅시다.
          bash$ ls -l | ./uppercase.sh -RW-RW-R-- 1 BOZO BOZO 109 APR 7 19:49 1.TXT -RW-RW-R-- 1 BOZO BOZO 109 APR 14 16:48 2.TXT -RW-R--R-- 1 BOZO BOZO 725 APR 20 20:56 DATA-FILE
          
참고: 파이프로 묶인 각 프로세스의 표준출력은 다음 명령어의 표준입력으로 읽혀야 합니다. 이런식으로 동작하지 않는다면 데이타의 흐름은 블럭될 것이고 파이프는 생각했던대로 동작하지 않을 것입니다.
          cat file1 file2 | ls -l | sort # "cat file1 file2" 의 출력은 나타나지 않습니다.
          
파이프는 자식 프로세스로 돌기 때문에 스크립트의 변수값을 바꿀 수가 없습니다.
          variable="initial_value" echo "new_value" | read variable echo "variable = $variable" # variable = initial_value
          
파이프로 연결된 명령어중 하나가 취소된다면 전체 실행이 취소되는데 이를 broken pipe라고 하고, 이 때 SIGPIPE 시그널이 발생 됩니다.
>|
강제 재지향(noclobber 옵션이 켜 있더라도). 파일이 이미 존재하더라도 강제로 덮어 쓰게 합니다.
&
작업을 백그라운드로 돌리기. 명령어 뒤에 &를 붙여 주면 백그라운드로 실행됩니다.
          bash$ sleep 10 & [1] 850 [1]+ Done sleep 10
          
경고
스크립트에서 어떤 명령어를 백그라운드로 돌리게 되면 키가 눌리길 기다리면서 스크립트가 멈춰버립니다. 다행스럽게도 이런 상황을 피해 갈 수 있는 방법이 있습니다.
-
표준입력(stdin)과 표준출력(stdout) 서로간의 재지향. [대쉬]
          (cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -) # 한 디렉토리의 전체 파일 구조를 다른 디렉토리로 옮김 # [Alan Cox <a.cox@swansea.ac.uk> 제공, 약간의 수정] # 1) cd /source/directory 옮겨질 파일들이 있는 소스 디렉토리 # 2) && "And-list": 'cd' 명령이 성공하면 다음 명령어가 실행됨 # 3) tar cf - . 'c' 옵션은 새 아카이브를 만들라는 명령어 # 'f'(file) 옵션은 그 뒤에 나오는 '-'에 의해 타켓 파일을 표준 출력으로 지정해 주고, # 현재 디렉토리 트리(.)를 대상으로 하게 합니다. # 4) | 파이프를 걸고 # 5) ( ... ) 서브쉘 # 6) cd /dest/directory 옮길 디렉토리로 이동 # 7) && 위에서 설명했던 "And-list" # 8) tar xpvf - 아카이브를 풀고('x'), 소유권과 파일 퍼미션을 유지(' p')하고 # 표준출력으로 메세지를 많이(verbose) 찍게 하고('v') # 표준입력에서 데이터를 읽어 들임('f' 다음의 '-') # # 'x'는 명령어고, 'p', 'v', 'f'는 옵션입니다. 주의하세요. # 헥헥~~~~ # 똑같지만 더 우아한 방법: # cd source-directory # tar cf - . | (cd ../target-directory; tar xzf -) # # cp -a /source/directory /dest 도 같은 결과가 나옵니다.
          
          bunzip2 linux-2.4.3.tar.bz2 | tar xvf - # --tar 파일을 풀어서 -- | --"tar" 에게 넘김 -- # "tar"에 "bunzip2"를 처리하는 패치가 안 돼 있다면 # 파이프를 써서 두 단계로 나누어 처리를 해 줘야 합니다. # 여기서는 "bzip"으로 압축된 커널 소스를 푸는 것을 보여줍니다.
          
여기서 쓰인 "-"는 Bash 연산자가 아니고, tar나 cat 같은 몇몇 유닉스 유틸리티들이 인식해서 표준출력으로 쓰도록 해주는 옵션임에 주의하세요.
파일명이 나와야 할 곳에 -이 나오면 표준출력으로 결과를 재지향하든지(tar cf에 서 가끔 쓰죠), 실제 파일에서 입력을 받지 않고 표준입력에서 받도록 재지향 하게 해 줍니다. 주로 파일을 다루는 유틸리티들을 파이프에서 필터로 쓸 때 이 방법을 씁니다.
          bash$ file 사용법: file [-bciknvzL] [-f namefile] [-m magicfiles] file...
          
file이 명령어줄에서 옵션없이 불리면 에러 메세지를 내면서 실패합니다.
          bash$ file - #!/bin/bash standard input: Bourne-Again shell script text executable
          
이 번에는 자신의 입력을 표준입력에서 받고 그 결과를 필터링 합니다.
- 는 표준출력을 다른 명령어로 파이프 시키는데 쓰일 수 있습니다. 이렇게 하면 파일의 앞쪽에 줄을 삽입하기같은 묘기도 부릴 수 있습니다.
diff를 써서 섹션을 가진 두 파일을 비교해 보기 바랍니다.
grep bash file1 | diff file2 -
마지막으로, tar에 -를 쓴 현실적인 예제입니다.
예 4-3. 최근 하루동안 변경된 파일들을 백업하기
          #!/bin/bash # 현재 디렉토리의 모든 파일중 최근 24시간 안에 변경된 파일들을 #+ 타르로 묶고 gzip으로 압축한 "타르볼"로 백업 NOARGS=0 E_BADARGS=65 if [ $# = $NOARGS ] then echo "사용법: `basename $0` filename" exit $E_BADARGS fi tar cvf - `find . -mtime -1 -type f -print` > $1.tar gzip $1.tar # 너무 많은 파일이 발견되거나 파일명에 빈 칸이 들어있다면 위의 코드는 #+ 실패할 수도 있다고 Stephane Chazelas 가 지적해 주었습니다. # 그가 제안한 다른 방법은 다음과 같습니다: # ------------------------------------------------------------- # find . -mtime -1 -type f -print0 | xargs -0 tar rvf "$1.tar" # "find"의 GNU 버전을 쓴 방법. # find . -mtime -1 -type f -exec tar rvf "$1.tar" '{}' \; # 다른 유닉스에서도 쓸 수 있지만 더 느린 방법. exit 0
          
경고
파일명이 -로 시작하는 파일이 - 재지향 연산자와 같이 쓰이면 문제가 생길 수도 있습니다. 스크립트 내에서 이런 사항을 확인한 다음에 ./-FILENAME이나 $PWD/-FILENAME등으로 바꿔서 처리해 줘야 합니다.
또한, 변수의 값이 -로 시작할 경우에도 비슷한 문제가 생길 수 있습니다.
          var="-n" echo $var # "echo -n" 처럼 해석이 되어 결과가 안 나타납니다.
          
-
바로 전 작업 디렉토리. [대쉬] cd - 라고 하면 $OLDPWD 환경 변수를 이용해서 바로 전 작업 디렉토리로 옮겨갑니다.
경고
방금 위에서 설명한 재지향 연산자인 "-"와 헷갈리면 안 됩니다. "-"는 문맥에 따라 알맞게 해석됩니다.
-
빼기. 산술 연산에서 쓰이는 빼기 부호.
=
          a=28 echo $a # 28
          
"="가 다른 문맥에서 쓰이면 문자열 비교 연산자입니다.
+
더하기. 덧셈 산술 연산자.
+가 다른 문맥에서 쓰이면 정규 표현식 연산자입니다.
%
나머지(modulo). 나눗셈의 나머지 산술 연산자.
%가 다른 문맥에서 쓰이면 패턴 매칭 연산자입니다.
~
홈 디렉토리. [틸드] 이 문자는 $HOME 내부 변수에 해당합니다. ~bozo 는 bozo의 홈 디렉토리를 나타내고ls ~bozo 는 그 홈 디렉토리의 내용을 보여줍니다. ~/ 은 현재 사용자의 홈 디렉토리를 나타내고, ls ~/ 는 그 홈 디렉토리의 내용을 보여줍니다.
          bash$ echo ~bozo /home/bozo bash$ echo ~ /home/bozo bash$ echo ~/ /home/bozo/ bash$ echo ~: /home/bozo: bash$ echo ~nonexistent-user ~nonexistent-user
          
~+
현재 작업 디렉토리. 이 문자는 $PWD 내부 변수에 해당합니다.
~-
바로 전 작업 디렉토리. 이 문자는 $OLDPWD 내부 변수에 해당합니다.
제어 문자
터미널이나 텍스트 디스플레이의 동작을 변경. 제어 문자는 CONTROL + key 조합으로 나타낼 수 있습니다.
  • Ctl-C
    포그라운드 작업을 끝냄.
  • Ctl-D
    쉘에서 로그 아웃(exit와 비슷함).
    "EOF" (파일끝, end of file). 표준입력에서 들어오는 입력을 끝냄.
  • Ctl-G
    "벨(BEL)"(삑 소리).
  • Ctl-H
    백스페이스(backspace).
  • Ctl-J
    캐리지 리턴(carriage return).
  • Ctl-L
    폼피드(formfeed, 터미널 화면을 청소). clear 명령어와 똑같은 효과.
  • Ctl-M
    뉴라인(Newline).
  • Ctl-U
    입력줄을 지움.
  • Ctl-Z
    포그라운드 작업을 잠시 멈춤.
공백문자(whitespace)
명령어나 변수의 구분자 역할. 공백문자는 빈칸, 탭, 빈줄들의 어떠한 조합들로 이루어져 있습니다. 변수 할당같은 상황에서 공백문자를 쓰면 문법 에러가 납니다.
빈줄은 스크립트 동작에 아무 영향도 주지 않기 때문에 기능별로 구분시켜서 보기 좋게 하는데 쓸 수 있습니다.
특수 변수인 $IFS는 어떤 명령어의 입력 필드를 구분해 주는데 이 변수의 디폴트값은 공백문자입니다.

주석

[1]
쉘은 중괄호 확장을 시도할 것이고, 명령어는 확장된 결과에 따라 동작합니다.
[2]
예외: 중괄호로 묶인 코드 블럭이 파이프의 일부분으로 돈다면 서브쉘로 돌 수도 있습니다.
          ls | { read firstline; read secondline; } # 에러. 중괄호 안의 코드 블럭은 서브쉘로 돌기 때문에 "ls"의 결과가 # 블럭 안의 변수로 전달될 수 없습니다. echo "첫번째 줄은 $firstline; 두번째 줄은 $secondline" # 동작하지 않을 겁니다. # Thanks, S.C.