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