Parameter Expansion

프로그래밍 언어에서 함수를 선언할 때 인수를 받는 부분에 사용되는 이름을 매개변수 ( parameter ) 라 하고 함수 내에서 임의로 저장공간을 만들 때 사용되는 이름을 변수 ( variable ) 라고 하듯이 매개변수 와 변수는 사용상에는 차이가 없을지라도 내포하는 의미는 다르다고 할 수 있습니다. 편의상 본문에서 변수확장 이라는 용어를 사용하였지만 정확한 의미로는 매개변수 확장이 맞다고 할 수 있습니다. 아래서 살펴보겠지만 매개변수 확장은 단순히 변수 이름을 값으로 바꾸는 것이 아니라 여러 가지 유용한 연산 작업을 수행합니다.

제공하는 기능의 수가 많은 것 같으나 여러 단계의 명령을 거쳐야 얻을 수 있는 결과를 매개변수 확장을 이용하면 간단히 처리할 수 있으므로 shell script 에서 많이 사용되는 주요 기능이라고 할 수 있습니다.

sh 에서는 사용할 수 없는 기능
substring expansion, search and replace, indirection, case modification

기본 사용법

AA=cde 대입연산은 AA 라는 변수를 만들고 'cde' 라는 값을 저장합니다. 이 값을 사용하기 위해서는 $ 문자를 변수이름 앞에 붙여 구분하는데 이것만으로는 부족할 때가 있습니다. 가령 변수 AA 값을 이용해 'abcdefg' 스트링을 만들고자 할때 echo "ab$AAfg" 와 같이 한다면 변수 'AA' 가 아닌 'AAfg' 값이 적용되어 원하는 결과가 나오지 않게됩니다. 따라서 나머지 스트링과 구분하기 위해 변수이름에 { } 중괄호를 사용할 수 있게 하였는데 이것은 매개변수 확장을 위한 표현식을 작성할 때도 사용됩니다.

$ AA=cde

$ echo "ab$AAfg"       # 현재 변수 $AAfg 값은 null 이므로 'ab' 만 나온다. 
ab

$ echo "ab${AA}fg"     # '{ }' 을 이용하면 나머지 스트링과 구분할 수 있다.
abcdefg

String length

${#PARAMETER}

변수이름 앞에 # 문자를 붙이면 변수가 가지는 값의 문자 수를 나타냅니다. array 의 @ , * 경우에는 전체 원소 개수를 나타냅니다.

$ AA="hello world"
$ echo ${#AA}
11

Array 의 경우

$ BB=( Arch Ubuntu Fedora Suse )
$ echo ${#BB[1]}     # [1] 번째 원소 Ubuntu 의 문자 수
6
$ echo ${#BB[@]}     # array BB 의 전체 원소 개수
4

Substring removal

${PARAMETER#PATTERN}
${PARAMETER##PATTERN}
${PARAMETER%PATTERN}
${PARAMETER%%PATTERN}

변수가 가지는 값을 패턴을 이용해 매칭되는 부분을 잘라낼 때 사용합니다. # 기호는 패턴을 앞에서부터 적용한다는 뜻이고 % 기호는 패턴을 뒤에서부터 적용한다는 뜻입니다. 기호가 두 개로 중복된 것은 longest match 를 뜻하고 하나짜리는 shortest match 를 뜻합니다.

AA="this.is.a.inventory.tar.gz"

$ echo ${AA#*.}               # 앞에서 부터 shortest match
is.a.inventory.tar.gz

$ echo ${AA##*.}              # 앞에서 부터 longest match
gz

$ echo ${AA%.*}               # 뒤에서 부터 shortest match
this.is.a.inventory.tar

$ echo ${AA%%.*}              # 뒤에서 부터 longest match
this


# 디렉토리를 포함한 파일명에서 디렉토리와 파일명을 분리하기
AA="/home/bash/bash_hacker.txt"

$ echo ${AA%/*}               # 디렉토리 부분 구하기
/home/bash

$ echo ${AA##*/}              # 파일명 부분 구하기
bash_hacker.txt

Use a default value

${PARAMETER:-WORD}
${PARAMETER-WORD}

${AA:-linux} : AA 변수값을 사용합니다. 그런데 AA 변수가 존재하지 않거나, null 값을 갖는 경우 linux 를 사용합니다.

${AA-linux} : AA 변수값을 사용합니다. 그런데 AA 변수가 존재하지 않는 경우 linux 를 사용합니다. 다시 말해 이 표현식은 AA 값으로 null 도 허용하는것 입니다.

WORD 부분에서도 변수 확장을 사용할 수 있습니다.

참고로 :- 를 사용하는 것이 좋을 것 같은곳에 - 를 사용하는 경우를 볼 수가 있는데요 이것은 4.3BSD 와 예전 shell 에서 :- 를 처리하지 못하기 때문이라고 합니다.

$ AA=ubuntu

$ echo ${AA:-fedora}        # AA 에 값이 있으면 AA 값을 사용한다
ubuntu
$ echo ${AA-fedora}
ubuntu

$ unset AA

$ echo ${AA:-fedora}        # AA 가 unset 되어 없는 상태이므로  
fedora                      # 값은 fedora 가 된다.
$ echo ${AA-fedora}
fedora

$ AA=""

$ echo ${AA:-fedora}       # ':-' 는 null 은 값이 없는것으로 보고 fedora 를 사용한다.
fedora
$ echo ${AA-fedora}        # '-' 는 null 도 값으로 취급하기 때문에
                           # 아무것도 표시되지 않는다.


# $TMPDIR 변수값이 없으면 /tmp 디렉토리에서 myfile_* 을 삭제 
$ rm -f ${TMPDIR:-/tmp}/myfile_*

# WORD 부분에도 명령치환을 사용할 수 있다.
$ AA=${AA:-$(date +%Y)}

# FCEDIT 변수값이 없으면 EDITOR 변수값을 사용하고 EDITOR 변수값도 없으면 vi 를 사용
$ AA=${FCEDIT:-${EDITOR:-vi}}

Array 의 경우

$ AA=( 11 22 33 )

$ echo ${AA[@]:-44 55 66}    # AA 에 값이 있으면 AA 값을 사용한다
11 22 33
$ echo ${AA[@]-44 55 66}
11 22 33

$ AA=()                      # 또는 unset -v AA

$ echo ${AA[@]:-44 55 66}    # AA 가 unset 되어 존재하지 않는 상태이므로
44 55 66                     # 값은 44 55 66 이 된다.
$ echo ${AA[@]-44 55 66}
44 55 66

$ AA=("")

$ echo ${AA[@]:-44 55 66}    # ':-' 는 null 은 값이 없는것으로 보고 44 55 66 을 사용한다
44 55 66
$ echo ${AA[@]-44 55 66}     # '-' 는 null 도 값으로 취급하기 때문에
                             # 아무것도 표시되지 않는다.

$ AA=("" 77 88)

$ echo ${AA[@]:-44 55 66}
77 88
$ echo ${AA[@]-44 55 66}
77 88

Assign a default value

${PARAMETER:=WORD}
${PARAMETER=WORD}

이것은 위의 Use a default value 와 동일하게 동작합니다. 하지만 차이점이 하나 있는데요. 바로 대체값을 사용하게 될때 그값을 AA 변수에도 대입한다는 것입니다.

AA=""

$ echo ${AA:-linux}
linux
$ echo $AA             # ':-' 는 AA 변수에 값을 대입하지 않는다.

$ echo ${AA:=linux}
linux
$ echo $AA             # ':=' 는 AA 변수에 값을 대입한다.
linux


# $TMPDIR 값이 없으면 /tmp 를 사용하고 TMPDIR 변수에 대입
$ cp myfile ${TMPDIR:=/tmp}

# $TMPDIR 변수가 설정된 상태이므로 cd 할 수 있다.
$ cd $TMPDIR

Array[@] 값은 대입되지 않습니다.

$ AA=()

$ echo ${AA[@]:=11 22 33}
bash: AA[@]: bad array subscript
11 22 33

Use an alternate value

${PARAMETER:+WORD}
${PARAMETER+WORD}

${AA:+linux} : AA 변수가 값을 가지고 있으면 linux 를 사용합니다. 변수가 존재하지 않거나 값이 null 이면 null 을 리턴합니다.

${AA+linux} : AA 변수가 값을 가지고 있으면 ( null 값 포함 ) linux 를 사용합니다. 변수가 존재하지 않으면 null 을 리턴합니다.

$ AA=hello

$ echo ${AA:+linux}       # AA 에 값이 있으므로 대체값 linux 를 사용한다.
linux
$ echo ${AA+linux}
linux

$ AA=""

$ echo ${AA:+linux}       # ':+' 는 null 은 값으로 취급하지 않기 때문에 null 을 리턴한다.

$ echo ${AA+linux}        # '+' 는 null 도 값으로 취급하기 때문에 대체값 linux 를 사용한다.
linux

$ unset AA

$ echo ${AA:+linux}       # 변수가 존재하지 않으므로 null 을 리턴한다.

$ echo ${AA+linux}


# 함수이름을 갖는 FUNCNAME 변수가 있을 경우 뒤에 '()' 를 붙여서 프린트 하고 싶다면
echo ${FUNCNAME:+${FUNCNAME}()}

Display error if null or unset

${PARAMETER:?[error message]}
${PARAMETER?[error message]}

${AA:?error message} : AA 변수값을 사용합니다. 그런데 AA 변수가 존재하지 않거나 null 값이면 error message 를 출력하고 스크립트 실행이 종료됩니다. 이때 종료 상태 값으로 1 (bash) or 2 (sh) 가 설정됩니다.

${AA?error message} : AA 변수값을 사용합니다. 그런데 AA 변수가 존재하지 않으면 error message 를 출력하고 스크립트 실행이 종료됩니다. 이때 종료 상태 값으로 1 (bash) or 2 (sh) 가 설정됩니다.

error message 를 생략하면 'parameter null or not set' 메시지가 사용됩니다.

$ AA=hello

$ echo ${AA:?null or not set}      # AA 에 값이 있으므로 AA 값을 사용한다
hello
$ echo ${AA?not set}
hello

$ AA=""

$ echo ${AA:?null or not set}       # ':?' 는 null 은 값으로 취급하지 않기 때문에
bash: AA: null or not set           # error message 를 출력하고 $? 값으로 1 을 리턴한다 
$ echo $?
1
$ echo ${AA?not set}                # '?' 는 null 도 값으로 취급하기 때문에 
                                    # 아무것도 표시되지 않는다.

$ unset AA

$ echo ${AA:?null or not set}       # 변수가 존재하지 않는 상태이므로 
bash: AA: null or not set           # 모두 error message 를 출력 후 종료한다.
$ echo $?
1
$ echo ${AA?not set}
bash: AA: not set
$ echo $?
1
$ echo ${AA?}                        # error message 는 생략할 수 있다.
bash: AA: parameter null or not set


# 예제
case ${AA:?"missing pattern; try '$0 --help' for help"} in
    (abc) ... ;;
    (*) ... ;;
esac

이후부터는 bash 전용

Substring expansion

${PARAMETER:OFFSET}
${PARAMETER:OFFSET:LENGTH}

변수가 가지는 값에서 특정부분을 뽑아낼때 사용합니다. offset 위치에서부터 length 길이 만큼을 뽑아냅니다. offset 은 0 부터 시작하고 length 값을 주지 않으면 끝까지 해당됩니다.

offset 과 length 를 음수로 줄수 있는데 이경우 카운트를 값의 시작점에서부터 하지 않고 끝점에서부터 좌측 방향으로 하며 length 만큼 제외 하게 됩니다. 이때 사용되는 음수의 - 기호는 매개변수 확장에서 사용되는 :- 기호와 겹치므로 ( ) 를 사용해야 합니다.

AA="Ubuntu Linux"

$ echo "${AA:1}"
buntu Linux
$ echo "${AA:2:4}"
untu
$ echo "${AA:(-5)}"
Linux
$ echo "${AA:(-5):2}"
Li
$ echo "${AA:(-5):-1}"
Linu

# 변수 AA 값이 %foobar% 일경우 % 문자 제거하기
$ echo "${AA:1:-1}"
foobar

Array 의 경우

$ ARR=(11 22 33 44 55)

$ echo ${ARR[@]:1:2}
22 33

$ echo ${ARR[@]:2}
33 44 55

Positional parameters 의 경우는 index 가 1 부터 시작합니다.

$ set -- 11 22 33 44 55

$ echo ${@:3}
33 44 55

$ echo ${@:2:2}
22 33

Search and replace

${PARAMETER/PATTERN/STRING}
${PARAMETER//PATTERN/STRING}
${PARAMETER/PATTERN}
${PARAMETER//PATTERN}

변수가 가지는 값을 패턴을 이용해 매칭되는 부분을 다른 스트링으로 바꾸거나 삭제할때 사용합니다. 매칭되는 부분이 여러군대 나타날수 있는데 // 기호는 모두를 나타내고 /첫번째 하나만 나타냅니다. 바꾸는 스트링 값을 주지 않으면 매칭되는 부분이 삭제됩니다. array[@] 의 각 원소에 대해서도 적용할수 있습니다.

$ AA="Arch Linux Ubuntu Linux Fedora Linux"

$ echo ${AA/Linux/Unix}               # Arch Linux 만 바뀌였다.
Arch Unix Ubuntu Linux Fedora Linux

$ echo ${AA//Linux/Unix}              # Linux 가 모두 Unix 로 바뀌였다
Arch Unix Ubuntu Unix Fedora Unix

$ echo ${AA/Linux}                    # 바꾸는 스트링을 주지 않으면 매칭되는 부분이 삭제된다.
Arch Ubuntu Linux Fedora Linux

$ echo ${AA//Linux}                  
Arch Ubuntu Fedora

-----------------------------------------
$ AA="Linux Ubuntu Linux Fedora Linux"

$ echo ${AA/#Linux/XXX}               # '#Linux' 는 맨 앞 단어를 의미
XXX Ubuntu Linux Fedora Linux

$ echo ${AA/%Linux/XXX}               # '%Linux' 는 맨 뒤 단어를 의미
Linux Ubuntu Linux Fedora XXX

$ AA=12345

$ echo ${AA/#/X}
X12345

$ echo ${AA/%/X}
12345X

$ AA=X12345X

$ echo ${AA/#?/}
12345X

$ echo ${AA/%?/}
X12345

Array[@] 는 각 원소별로 적용됩니다.

$ AA=( "Arch Linux" "Ubuntu Linux" "Fedora Linux" )

$ echo ${AA[@]/u/X}                   # Ubuntu Linux 는 첫번째 'u' 만 바뀌었다
Arch LinXx UbXntu Linux Fedora LinXx

$ echo ${AA[@]//u/X}                  # 이제 모두 바뀌였다
Arch LinXx UbXntX LinXx Fedora LinXx

Case modification

${PARAMETER^}
${PARAMETER^^}
${PARAMETER,}
${PARAMETER,,}

${PARAMETER^} : 단어의 첫 문자를 대문자로 변경합니다.
${PARAMETER^^} : 단어의 모든 문자를 대문자로 변경합니다.
${PARAMETER,} : 단어의 첫 문자를 소문자로 변경합니다.
${PARAMETER,,} : 단어의 모든 문자를 소문자로 변경합니다.

$ AA=( "ubuntu" "fedora" "suse" )

$ echo ${AA[@]^}
Ubuntu Fedora Suse

$ echo ${AA[@]^^}
UBUNTU FEDORA SUSE

$ AA=( "UBUNTU" "FEDORA" "SUSE" )

$ echo ${AA[@],}
uBUNTU fEDORA sUSE

$ echo ${AA[@],,}
ubuntu fedora suse

Indirection

${!PARAMETER}

스크립트 실행 중에 스트링으로 변수 이름을 만들어서 사용할 수 있습니다.

$ hello=123

$ linux=hello

$ echo ${linux}
hello

$ echo ${!linux}    # '!linux' 부분이 'hello' 로 바뀐다고 생각하면 됩니다. 
123

sh 에서는 다음과 같이 eval 명령을 이용해서 indirection 을 사용할 수 있습니다.

$ hello=123

$ linux=hello

$ eval echo '$'$linux
123
----------------------

if test "$hello" -eq  "$(eval echo '$'$linux)"; then
...
fi

함수에 전달된 인수를 표시할 때

---------- args.sh ------------
#!/bin/bash

for (( i = 0; i <= $#; i++ )) 
do
    echo \$$i : ${!i}              # ${$i} 이렇게 하면 안됩니다.
done

-------------------------------

$ args.sh 11 22 33
$0 : ./args.sh
$1 : 11                    
$2 : 22
$3 : 33

함수에 array 인자를 전달할 때도 사용할 수 있다.

#!/bin/bash

foo() {
    echo "$1"

    local ARR=( "${!2}" )      # '!2' 부분이 'AA[@]' 로 바뀐다.

    for v in "${ARR[@]}"; do
        echo "$v"
    done

    echo "$3"
}

AA=(22 33 44)
foo 11 'AA[@]' 55

################ output ###############

11
22
33
44
55