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 를 뜻합니다.
PATTERN 에는
*
,?
,[ ]
glob 문자를 사용할 수 있습니다.
AA="this.is.an.inventory.tar.gz"
$ echo ${AA#*.} # 앞에서 부터 shortest match
is.an.inventory.tar.gz
$ echo ${AA##*.} # 앞에서 부터 longest match
gz
$ echo ${AA%.*} # 뒤에서 부터 shortest match
this.is.an.inventory.tar
$ echo ${AA%%.*} # 뒤에서 부터 longest match
this
# 디렉토리를 포함한 파일명에서 디렉토리와 파일명을 분리하기
AA="/home/mug896/bash.txt"
$ echo ${AA%/*} # 디렉토리 부분 구하기
/home/mug896
$ echo ${AA##*/} # 파일명 부분 구하기
bash.txt
# '~' 문자는 escape 해야한다.
$ AA="~/tmp"
$ echo ${AA#~}
~/tmp
$ echo ${AA#\~}
/tmp
--------------------------------------------------------
$ AA=$( echo -e "title\n111\n222\n333" )
$ echo "$AA"
title
111
222
333
$ echo "${AA%%$'\n'*}" # 첫번째 라인 구하기
title
$ echo "${AA#*$'\n'}" # 나머지 라인 구하기
111
222
333
# sh 에서는 $'\n' 를 사용할 수 없으므로 먼저 newline 변수를 설정해 사용
$ nl='
'
$ echo "${AA%%$nl*}"
title
확장패턴 사용 예제
$ AA=" 123 456" # AA 변수값의 leading space 와 trailing space 를 제거
$ echo "x${AA##+( )}x" # +( ) 확장패턴 사용
x123 456x # +( ) 패턴에 대해서 longest match 가 되므로
# leading space 가 모두 제거된다.
$ AA="123 456 "
$ echo "x${AA%%+([[:blank:]])}x"
x123 456x
$ echo "x${AA%%+( |$'\t')}x" # 위와 동일
x123 456x
--------------------------------
$ val=aa-bb_cc.x
$ echo ${val##*@(-|_)} # @( ) 확장패턴 사용
cc.x
$ val=aa_bb-cc.x
$ echo ${val##*@(-|_)}
cc.x
array[@]
, array[*]
, $@
, $*
의 경우는 각각의 원소에 대해서 적용됩니다.
$ \ls /usr/bin/bas*
/usr/bin/base32 /usr/bin/base64 /usr/bin/basename /usr/bin/bashbug
$ AA=( /usr/bin/bas* )
$ declare -p AA
declare -a AA=([0]="/usr/bin/base32" [1]="/usr/bin/base64" [2]="/usr/bin/basename"
[3]="/usr/bin/bashbug")
$ echo ${AA[@]##*/} # ${AA[@]}
base32 base64 basename bashbug
$ echo ${AA[1]##*/} # ${AA[1]}
base64
다음부터 이어지는 기능은 아래와 같이 변수 이름 옆에 공백 없이 연산자를 붙이고
이후에 값이나, 변수, 명령 치환 등을 사용할 수 있는 기능입니다.
연산자를 사용하는 방식에는 두 가지가 있는데 왼쪽과 같이 연산자에 :
를 붙이지 않는 것은
변수의 존재 여부만 체크하고 :
가 붙는 것은 더해서 변수값이 empty 인지도 체크합니다.
참고로 연산자에 :
를 사용하는 것은 나중에 추가된 기능이라고 합니다.
따라서 작성된 스크립트를 보다 보면 :-
나 :+
를 사용하는 것이 좋을 것 같은 곳에
-
나 +
를 사용하는 경우를 볼 수가 있는데 이것은 예전 shell 에서 :
를 처리하지 못하기 때문입니다.
Use a default value
${PARAMETER:-WORD}
${PARAMETER-WORD}
${AA:-linux}
: AA 변수값을 사용합니다. 그런데 AA 변수가 존재하지 않거나, null 값을 갖는 경우 linux 를 사용합니다. ( 물론 AA
대신에 ${AA[2]:-linux}
와 같이 array 값도 사용할 수 있습니다. )
# BB=${AA:-linux} 는 다음과 같은 것입니다.
if [ -n "$AA" ]; then
BB=$AA
else
BB=linux
fi
${AA-linux}
: AA 변수값을 사용합니다. 그런데 AA 변수가 존재하지 않을 경우 linux 를 사용합니다.
AA 변수가 존재하기만 하면 되므로 이 표현식은 AA 값으로 null 도 허용되는 것입니다.
# BB=${AA-linux} 는 다음과 같은 것입니다.
if [ -v AA ]; then
BB=$AA
else
BB=linux
fi
sh
에서는 test 명령에서 변수 존재 여부를 체크하는-v
옵션을 사용할 수 없는데
이 표현식을 활용하면 간접적으로 체크 할 수 있습니다.
예를 들어 BB=${AA-linux}
대입 연산에서 BB 변수가 linux 로 설정됐다면
현재 변수 AA
는 존재하지 않는 상태죠
$ AA=ubuntu
$ echo ${AA:-fedora} # AA 에 값이 있으면 AA 값을 사용한다
ubuntu
$ echo ${AA-fedora}
ubuntu
$ unset -v AA
$ echo ${AA:-fedora} # AA 가 unset 되어 존재하지 않는 상태이므로
fedora # 값은 fedora 가 된다.
$ echo ${AA-fedora}
fedora
$ AA=""
$ echo ${AA:-fedora} # ':-' 는 null 은 값이 없는것으로 보고 fedora 를 사용한다.
fedora
$ echo ${AA-fedora} # '-' 는 변수의 존재 여부만 체크하므로 아무것도 표시되지 않는다.
$
# $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 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} # '-' 는 변수만 존재하면 되므로 아무것도 표시되지 않는다.
$
연산자 왼쪽에는 공백이 있으면 오류가 되지만 오른쪽의 공백은 그대로 값에 포함됩니다. 이외에는 명령라인 에서와 동일하게 quotes 과 escape 이 처리됩니다. 값에 quotes 을 포함하려면 quotes 을 사용하거나 escape 해주면 됩니다. 그냥 간단히 생각해서 연산자 오른쪽에 있는 값이 문자 그대로( quotes, escape 포함 ) 연산에 사용된다고 보면 됩니다.
# foo=$val 는 $foo 값이 123 이되고 foo=\$val 는 $val, foo='$val' 는 $val 가 된다.
val=123 val=123
foo=${xxx:- $val \$val } foo=${xxx:- "$val" '$val' }
$ echo yyy"${foo}"zzz $ echo yyy"${foo}"zzz
yyy 123 $val zzz yyy 123 $val zzz
# 대입연산 이므로 변수를 quote 하지않아도 공백과 개행이 유지되고 globbing 이 발생하지 않는다.
val1="aa bb" val2="*"
foo=${xxx:-$val1} foo=${xxx:-$val2}
$ echo yyy "${foo}" zzz $ echo yyy "${foo}" zzz
yyy aa bb zzz yyy * zzz
# 대입연산이 아닐경우 quote 을 하지않으면 공백이 유지되지 않고 globbing 이 발생한다.
$ echo yyy ${xxx:-$val1} zzz $ echo yyy ${xxx:-$val2} zzz
yyy aa bb zzz yyy a.txt b.txt c.txt ...
$ echo yyy ${xxx:-"$val1"} zzz $ echo yyy ${xxx:-"$val2"} zzz
yyy aa bb zzz yyy * zzz
$ echo yyy "${xxx:-$val1}" zzz $ echo yyy "${xxx:-$val2}" zzz
yyy aa bb zzz yyy * zzz
arr=(11 "22 22" 33) # ${arr[@]} 를 double quote
$ args.sh ${xxx-${arr[@]}} $ args.sh ${xxx-"${arr[@]}"}
$1 : 11 $1 : 11
$2 : 22 $2 : 22 22
$3 : 22 $3 : 33
$4 : 33
# 명령치환 에서처럼 ${ ... } 안에서 quotes 이 중첩되어도 됩니다.
$ echo "${X-"${Y-"${Z-"foo bar"}"}"}"
foo bar
다음과 같이 quotes 을 이용하면 multi-line 으로 작성할 수도 있습니다.
$ script=${NOT_EXIST-'
arr = []
while 1:
try:
line = input()
arr.append(int(line))
except EOFError:
break
print(f"max : {max(arr)}, min : {min(arr)}, sum : {sum(arr)}")
'}
$ shuf -i 1-10 | python3 -c "$script"
max : 10, min : 1, sum : 55
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
# 변수값이 설정되어 있을 경우 기존 값을 사용하지만 그렇지 않을 경우 값을 설정
: ${BIN_PATH:=/usr/local/bin}
: ${CONFIG_PATH:=/usr/local/www}
: ${LOGFILE:=/var/log/abc.log}
# 다음은 변수가 존재하지 않을 경우 설정
: ${CP="cp -f"}
: ${ECHO="printf %s\n"}
: ${MV="mv -f"}
: ${RM="rm -f"}
# 실행 결과로 AA 값이 not_set 으로 설정됐다면 변수 AA 는 존재하지 않는 상태였다는 것을 알수있다.
: ${AA=not_set}
# 현재 FOO 변수가 존재하지 않을 경우 존재하는 상태로 된다.
: ${FOO=}
Array 값은 대입되지 않습니다.
$ AA=()
$ : ${AA:=(11 22 33)}
$ echo ${AA[1]}
$ echo $AA # (11 22 33) 이 AA 변수값이 된다.
(11 22 33)
Use an alternate value
${PARAMETER:+WORD}
${PARAMETER+WORD}
${AA:+linux}
: AA 변수가 값을 가지고 있으면 linux 를 사용합니다. 변수가 존재하지 않거나 값이 null 이면 null 을 리턴합니다.
# BB=${AA:+linux} 는 다음과 같은 것입니다.
if [ -n "$AA" ]; then
BB=linux
else
BB=""
fi
${AA+linux}
: AA 변수가 존재하기만 하면 linux 를 사용합니다. 변수가 존재하지 않으면 null 을 리턴합니다.
# BB=${AA+linux} 는 다음과 같은 것입니다.
if [ -v AA ]; then
BB=linux
else
BB=""
fi
$ AA=hello
$ echo ${AA:+linux} # AA 에 값이 있으므로 대체값 linux 를 사용한다.
linux
$ echo ${AA+linux}
linux
$ AA=""
$ echo ${AA:+linux} # ':+' 는 null 은 값으로 취급하지 않기 때문에 null 을 리턴한다.
$ echo ${AA+linux} # '+' 는 변수가 존재하기만 하면 되므로 대체값 linux 가 사용된다.
linux
$ unset -v AA
$ echo ${AA:+linux} # 변수가 존재하지 않으므로 null 을 리턴한다.
$ echo ${AA+linux}
-----------------------
# 함수이름을 갖는 FUNCNAME 변수가 있을 경우 뒤에 '()' 를 붙여서 프린트 하고 싶다면
echo ${FUNCNAME:+$FUNCNAME()}
# $debian_chroot 변수에 값이 존재하면 '( )' 가 붙어서 출력되므로 다음과 같게 되고
# PS1='($debian_chroot)\u@\h:\w\$ '
# $debian_chroot 변수에 값이 존재하지 않으면 다음과 같게 된다.
# PS1='\u@\h:\w\$ '
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
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 를 출력하고
$ echo $? # 종료 상태 값으로 1 을 리턴한다.
1
$ echo ${AA?not set} # '?' 는 변수가 존재하기만 하면 되므로
# 아무것도 표시되지 않는다.
$ unset -v 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
$ echo ${AA?}
bash: AA: parameter 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 만큼 제외
하게 됩니다.
이때 사용되는 음수의 -
기호는 매개변수 확장에서 사용되는 :-
기호와 겹치므로 공백을 두거나 ( )
를 사용해야 합니다.
offset 이나 length 값을 변수로 사용할 때는
$
문자는 붙이지 않아도 됩니다.
AA="Ubuntu Linux"
$ echo "${AA:1}"
buntu Linux
$ echo "${AA:2:4}"
untu
$ echo "${AA: -5}" 또는 "${AA:(-5)}"
Linux
$ echo "${AA: -5:2}" 또는 "${AA:(-5):2}"
Li
$ echo "${AA: -5:-1}" 또는 "${AA:(-5):-1}"
Linu
# 변수 AA 값이 %foobar@ 일경우
$ echo "${AA: -1}" # 마지막 문자 추출
@
$ echo "${AA:0:1}" # 첫번째 문자 추출
%
$ echo "${AA:1:-1}" # 양 끝문자 제거
foobar
# 다음과 같은경우 오류가 발생할 수 있습니다.
$ AA=a
$ echo ${AA:1:-1}
bash: -1: substring expression < 0
Array 의 경우 ( length 에 음수 사용불가 )
$ ARR=(11 22 33 44 55)
$ echo ${ARR[@]:1:2}
22 33
$ echo ${ARR[@]:2}
33 44 55
Positional parameters 의 경우는 index 가 1 부터 시작합니다. ( length 에 음수 사용불가 )
$ 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[@]
, array[*]
, $@
, $*
의 경우는 각각의 원소에 대해서 적용됩니다.
regex 에서처럼
&
문자는 패턴에 의해 매칭된 값을 나타냅니다.
$ AA="Arch Linux Ubuntu Linux Fedora Linux"
$ echo ${AA/Linux/XXX} # Arch Linux 만 바뀌였다.
Arch XXX Ubuntu Linux Fedora Linux
$ echo ${AA//Linux/XXX} # Linux 가 모두 XXX 로 바뀌였다
Arch XXX Ubuntu XXX Fedora XXX
$ echo ${AA/Linux} # 바꾸는 스트링을 주지 않으면 매칭되는 부분이 삭제된다.
Arch Ubuntu Linux Fedora Linux
$ echo ${AA//Linux}
Arch Ubuntu Fedora
$ AA=foo456
$ echo ${AA//[^0-9]} # 숫자가 아닌 문자를 모두 삭제
456
----------------------------------------------------
$ 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
$ BB=AB12345CD
$ echo ${BB/#+([[:alpha:]])/} # +( ) 확장패턴을 이용해 [:alpha:] 문자들을 삭제
12345CD
$ echo ${BB/%+([[:alpha:]])/}
AB12345
-------------------------------------
$ CC=foobar
$ echo ${CC/f??/&-} # "&" 문자는 f?? 패턴의 매칭값인 foo 가 된다.
foo-bar
$ echo ${CC/f??/\&-}
&-bar
Array 는 각각의 원소별로 적용됩니다.
$ AA="aaa.c bbb.c ccc.c" # AA 가 array 가 아닐경우.
$ echo ${AA/#/common/}
common/aaa.c bbb.c ccc.c
$ AA=( aaa.c bbb.c ccc.c ) # AA 가 array 일 경우 각각의 원소에 적용된다.
$ echo ${AA[@]/#/common/}
common/aaa.c common/bbb.c common/ccc.c
---------------------------------------------------
$ CC=( "Arch Linux" "Ubuntu Linux" "Fedora Linux" )
$ echo ${CC[@]/u/X} # Ubuntu Linux 는 첫번째 'u' 만 바뀌었다
Arch LinXx UbXntu Linux Fedora LinXx
$ echo ${CC[@]//u/X} # // 를 사용해 모두 바뀌였다
Arch LinXx UbXntX LinXx Fedora LinXx
---------------------------------------------------
$ OPT=( -a --bbb -bc --dd-dd )
$ echo ${OPT[@]/#--*/}
-a -bc
$ echo ${OPT[@]/#-[^-]*/}
--bbb --dd-dd
Array 처리결과를 array 변수에 대입하려면 항상 =( )
를 사용해야 합니다.
$ AA=( aaa.c bbb.c ccc.c )
$ BB=${AA[@]/#/common/} # BB 는 array 가 되지 않는다.
$ echo ${#BB[@]} : ${BB[1]} : $BB
1 : : common/aaa.c common/bbb.c common/ccc.c
$ BB=( "${AA[@]/#/common/}" ) # 대입시 `=( )` 를 사용해야 array 가 된다.
$ echo ${#BB[@]} : ${BB[1]} : $BB
3 : common/bbb.c : common/aaa.c
확장패턴 을 활용하면 regex 에서처럼 가변 길이의 문자도 매칭이 가능합니다.
$ AA=" # include <stdio.h>"
$ echo x${AA/#*( )#*( )include*( )/}x # *( ) 확장 패턴 사용
x<stdio.h>x
$ AA=" #include <stdio.h>"
$ echo x${AA/#*( )#*( )include*( )/}x
x<stdio.h>x
$ AA=" #include # include <stdio.h>"
$ echo x${AA/#+(*( )#*( )include*( ))/}x # +( ) 를 추가해 복수개 매칭
x<stdio.h>x
$ AA=" #include # include <stdio.h> # include"
$ echo x${AA//*( )#*( )include*( )/}x
x<stdio.h>x
Case modification
${PARAMETER^} : 첫 문자를 대문자로 변경합니다.
${PARAMETER^^} : 모든 문자를 대문자로 변경합니다.
${PARAMETER,} : 첫 문자를 소문자로 변경합니다.
${PARAMETER,,} : 모든 문자를 소문자로 변경합니다.
${PARAMETER~} : 첫 문자를 반전합니다.
${PARAMETER~~} : 모든 문자를 반전합니다.
변수값을 ,,
을 이용해 모두 소문자로 바꿔주면 대,소문자 구분없이 매칭을 할 수 있습니다.
$ AA=( "ubuntu" "fedora" "suse" ) $ AA=( "UBUNTU" "FEDORA" "SUSE" )
$ echo ${AA[@]^} $ echo ${AA[@],}
Ubuntu Fedora Suse uBUNTU fEDORA sUSE
$ echo ${AA[@]^^} $ echo ${AA[@],,}
UBUNTU FEDORA SUSE ubuntu fedora suse
Indirection
${!PARAMETER}
스크립트 실행 중에 스트링으로 변수 이름을 만들어서 사용할 수 있습니다.
$ hello=123
$ linux=hello
$ echo ${linux}
hello
$ echo ${!linux} # '!linux' 부분이 'hello' 로 바뀐다고 생각하면 됩니다.
123 # !linux -> $linux -> hello
$ echo ${!linux/2/X}
1X3
sh
에서는 다음과 같이 eval 명령을 이용해서 indirection 을 사용할 수 있습니다.
$ hello=123
$ linux=hello
$ eval echo '$'$linux
123
----------------------
if test "$hello" = "$(eval echo '$'$linux)"; then
...
fi
함수에 전달된 인수를 표시할 때
---------- args.sh ------------
#!/bin/bash
for (( i = 0; i <= $#; i++ ))
do
echo \$$i : ${!i} # !i -> $i -> 0,1,2,3
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[@]' 로 바뀐다.
# !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
Parameter transformation
${parameter@operator} : operator 에는 a, P, Q, E, A 가 있습니다.
1 . ${varname@a}
: 변수 속성을 반환합니다.
$ AA=(1 2 3)
$ echo ${AA@a} # indexed array
a
$ declare -A BB=( [ab]=100 [cd]=200 ) # associative array
$ echo ${BB@a}
A
$ declare -i CC=100 # integer 속성
$ echo ${CC@a}
i
$ declare -r CC # readonly 속성 추가
$ echo ${CC@a}
ir
$ DD=100
$ echo ${DD@a} # 일반 변수는 값이 없다
$
2 . ${varname@P}
PS1, PS2 ... 프롬프트 변수값으로 사용할 경우 결과가 어떻게 출력되는지 알아볼 수 있습니다.
$ myprompt='\u@\h \@ \w \\$'
$ echo ${myprompt@P}
mug896@EliteBook 07:57 PM /usr/local $
3 . ${varname@Q}
: 변수값을 $' '
형태로 출력해 줍니다.
$ cat tmp.txt $ set -- "foo bar" "bar zoo"
'hello'
"world" $ echo "$@"
foo bar bar zoo
$ AA=`< tmp.txt`
$ echo "${@@Q}"
$ echo "$AA" 'foo bar' 'bar zoo'
'hello'
"world"
$ echo "${AA@Q}"
$'\'hello\'\n"world"'
4 . ${varname@E}
: 변수값에 있는 모든 escape 문자를 확장해서 출력합니다.
$ foo='one\ntwo\n\tlast'
$ echo "${foo}"
one\ntwo\n\tlast
$ echo "${foo@E}"
one
two
last
5 . ${varname@A}
: 대입연산 형태로 출력해 줍니다.
$ foo=100
$ echo ${foo@A}
foo='100'
$ declare -i bar=100
$ echo ${bar@A}
declare -i bar='100'
$ zoo=( 11 22 33 )
$ echo ${zoo[@]@A}
declare -a zoo=([0]="11" [1]="22" [2]="33")
$ declare -A goo=( [ab]=100 [cd]=200 )
$ echo ${goo[@]@A}
declare -A goo=([ab]="100" [cd]="200" )
Quiz
Linux 와 Unix 운영체제 에서는 경로를 구분하는 문자로 /
를 사용하는데요.
Windows 운영체제 에서는 반대로 \
문자를 사용합니다.
다음과 같은 Windows 경로가 주어졌을때 Unix 경로로 변경하려면 어떻게 할까요?
$ project_dir="\Documents\Projects\Newsletters\Summer"
# '\' 문자는 escape 할때 사용되므로 '\\' 와 같이 escape 해야 한다.
$ echo "${project_dir//\\//}"
/Documents/Projects/Newsletters/Summer
2 .
16 진수에서 Most Significant Byte ( 제일 왼쪽 문자 ) 가 8 이상이면 sign bit 이 1 이 되기 때문에 음수가 되는데요. 16 진수 값을 부호 있는 정수값으로 표시하려면 어떻게 할까요?
# MSB 값이 8 미만이면 바로 hex 값을 정수로 출력하고
$ echo $(( 0x7f ))
127
$ echo $(( 0x7fff ))
32767
# MSB 값이 8 이상이면 음수가 되므로 입력된 수의 자리수에 맞추어 빼기를 해준다.
$ echo $(( 0x8f - 0xff - 1 )) # 2 자리
-113
$ echo $(( 0x8fff - 0xffff - 1 )) # 4 자리
-28673
hex2int () {
local hex msb
hex=${1#0[xX]} # 입력값이 0x, 0X 로 시작할 경우 제거
msb=${hex:0:1} # MSB 값 추출
if test $(( 0x$msb )) -ge 8; then
echo $(( 0x$hex - 0x${hex//?/f} - 1 )) # '?' 는 glob 문자
else
echo $(( 0x$hex ))
fi
}
##################### 실행 결과 ###################
$ hex2int 0xff
-1
$ hex2int 0xfe
-2
$ hex2int 0x79
121
$ hex2int 0x78
120
3 .
명령의 인수로 파일이 주어지면 파일에서 라인을 읽어들여 처리하고, 그렇지 않으면 stdin ( 터미널, 파이프, redirection ) 에서 라인을 읽어서 처리하는 명령을 만드는 것입니다.
$ func1 () { $ cat tmp.txt
local total_lines 111
while IFS= read -r line 222
do 333
total_lines+=$line$'\n'
done < "${1:-/dev/stdin}" || return
echo ----------------------
echo -n "$total_lines"
echo ----------------------
}
##################################################################
$ func1 tmp.txt # 인수로 파일이 주어지면 파일에서 라인을 읽어 들임
----------------------
111
222
333
----------------------
$ func1 # enter 후 라인 입력
111
222
333
# ctrl-d 로 입력 종료
----------------------
111
222
333
----------------------
$ func1 <<\@ $ echo -e "foo\nbar" | func1
> 111 ----------------------
> 222 foo
> 333 bar
> @ ----------------------
----------------------
111 $ func1 <<< hello
222 ----------------------
333 hello
---------------------- ----------------------