9.3. 매개변수 치환(Parameter Substitution)

변수를 조작하거나 확장시키기

${parameter}

변수인 parameter의 값이란 뜻으로서, $parameter라고 한 것과 같습니다. 어떤 문맥에서는 ${parameter}라고 확실히 써 줘야 동작하는 수도 있습니다.

문자열 변수들을 연결할 때 쓰일 수 있습니다.

your_id=${USER}-on-${HOSTNAME}
echo "$your_id"
#
echo "Old \$PATH = $PATH"
PATH=${PATH}:/opt/bin  # 스크립트가 도는 동안 $PATH 에 /opt/bin 을 추가.
echo "New \$PATH = $PATH"

${parameter-default}

매개변수가 세트되지 않았다면 default를 사용합니다.

echo ${username-`whoami`}
# $username이 여전히 세트되어 있지 않다면 `whoami`의 결과를 에코.

참고: 이렇게 하면 ${parameter:-default}라고 하는 것과 거의 비슷하지만 :이 있을 때는 매개변수가 선언만 되어 값이 널일 경우에도 기본값을 적용시킵니다.

#!/bin/bash

username0=
# username0 는 선언만 되고 널로 세트됐습니다.
echo "username0 = ${username0-`whoami`}"
# 에코 되지 않습니다.

echo "username1 = ${username1-`whoami`}"
# username1 는 선언되지 않았습니다.
# 에코 됩니다.

username2=
# username2 는 선언만 되고 널고 세트됐습니다.
echo "username2 = ${username2:-`whoami`}"
# 조건 테스트시 - 가 아니고 :- 를 썼기 때문에 에코 됩니다.

exit 0

${parameter=default}, ${parameter:=default}

매개변수가 세트 되어 있지 않다면 기본값으로 세트.

두 형태는 거의 비슷하지만 :이 있을 때는 위의 경우처럼 $parameter가 선언만 되고 값이 널일 경우에도 기본값으로 세트 시킨다는 차이점이 있습니다 [1]

echo ${username=`whoami`}
# "username" 변수를 `whoami`의 결과로 세트.

${parameter+alt_value}, ${parameter:+alt_value}

매개변수가 세트되어 있다면 alt_value를 쓰고 아니라면 널 스트링을 씁니다.

이 두 형태는 거의 비슷하지만 parameter가 선언되고 널일 경우에 :이 있고 없고의 차이가 나타납니다. 아래를 보세요.

echo "###### \${parameter+alt_value} ########"
echo

a=${param1+xyz}
echo "a = $a"      # a =

param2=
a=${param2+xyz}
echo "a = $a"      # a = xyz

param3=123
a=${param3+xyz}
echo "a = $a"      # a = xyz

echo
echo "###### \${parameter:+alt_value} ########"
echo

a=${param4:+xyz}
echo "a = $a"      # a =

param5=
a=${param5:+xyz}
echo "a = $a"      # a =
# a=${param5+xyz}    와 결과가 다르죠?

param6=123
a=${param6+xyz}
echo "a = $a"      # a = xyz

${parameter?err_msg}, ${parameter:?err_msg}

매개변수가 세트되어 있으면 그 값을 쓰고 아니라면 err_msg를 출력.

두 형태도 역시 비슷하지만 :parameter가 선언만 되고 널일 경우에만 차이점이 나타납니다.

예 9-10. 매개변수 치환과 : 쓰기

#!/bin/bash

#  몇개의 시스템 환경 변수 확인
#  예를 들어, 콘솔 사용자의 이름을 나타내는 $USER 가 세트 돼 있지 않다면,
#+ 시스템은 여러분을 인식하지 못합니다.

: ${HOSTNAME?} ${USER?} ${HOME?} ${MAIL?}
  echo
  echo "시스템 이름은 $HOSTNAME 입니다."
  echo "여러분의 이름은 $USER 입니다."
  echo "여러분의 홈디렉토리는 $HOME 입니다."
  echo "여러분의 메일 INBOX는 $MAIL 에 있습니다."
  echo
  echo "이 메세지를 읽고 있다면,"
  echo "중요한 환경 변수가 세트 되어 있다는 뜻입니다."
  echo
  echo

# ------------------------------------------------------

#  ${variablename?} 도 스크립트에서 변수가 세트 되어 있는지를 
#+ 확인할 수 있습니다.

ThisVariable=Value-of-ThisVariable
#  문자열 변수는 변수 이름에 쓸 수 없는 문자가
#+ 세트될 수도 있으니 주의하세요.
: ${ThisVariable?}
echo "ThisVariable 의 값은 $ThisVariable."
echo
echo


: ${ZZXy23AB?"ZZXy23AB 는 세트되지 않았습니다."}
#  ZZXy23AB 에 값이 세트되어 있지 않다면,
#+ 에러 메세지를 뿌리고 종료할 것입니다.

# 에러 메세지를 지정할 수 있습니다.
# : ${ZZXy23AB?"ZZXy23AB 는 세트되지 않았습니다."}


# 다음과 똑같은 결과:  dummy_variable=${ZZXy23AB?}
#                      dummy_variable=${ZZXy23AB?"ZXy23AB 는 세트되지 않았습니다."}
#
#                      echo ${ZZXy23AB?} >/dev/null



echo "위에서 스크립트가 종료됐기 때문에 이 메세지는 안 보입니다."

HERE=0
exit $HERE   # 여기서 종료되지 *않습니다*.

매개변수 치환과 확장. 다음 식들은 expr 문자열 연산중 match에 대해 부족한 부분을 보완해 줍니다 (예 12-6 참고). 주로, 파일 경로명을 파싱할 때 쓰입니다.

변수 길이/문자열조각(substring) 삭제

${#var}

문자열 길이 ($var의 문자 갯수). 배열의 경우에, ${#array}라고 하면 배열의 첫번째 요소의 길이를 알려줍니다.

참고: 예외:

  • ${#*}${#@}위치 매개변수의 갯수를 알려줍니다.

  • 배열에 대해 ${#array[*]}${#array[@]} 라고 하면 배열 요소의 갯수를 알려줍니다.

예 9-11. 변수의 길이

#!/bin/bash
# length.sh

E_NO_ARGS=65

if [ $# -eq 0 ]  # 이 스크립트에서는 명령어줄 인자가 필요합니다.
then
  echo "하나 이상의 명령어줄 인자가 필요합니다."
  exit $E_NO_ARGS
fi  

var01=abcdEFGH28ij

echo "var01 = ${var01}"
echo "var01 의 길이 = ${#var01}"

echo "스크립트로 넘어온 명령어줄 인자 갯수 = ${#@}"
echo "스크립트로 넘어온 명령어줄 인자 갯수 = ${#*}"

exit 0
${var#pattern}, ${var##pattern}

$pattern$var앞 부분과 가장 길거나 가장 짧게 일치하는 부분을 삭제.

예 A-6에서 발췌한 사용법 예제:
# "days-between.sh" 예제에서 쓰인 함수.
# 주어진 인자의 앞 부분에 들어있는 하나 이상의 0 을 삭제.

strip_leading_zero () # 날짜나 월의 앞 부분에 나오는 0을 삭제하지 않으면 
{                     # Bash 가 8진수로 해석하기 때문에(POSIX.2, sect 2.9.2.1)
  val=${1#0}          # 이렇게 해 주는게 좋습니다.
  return $val
}

다른 사용법 예제:
echo `basename $PWD`        # 현재 디렉토리의 basename.
echo "${PWD##*/}"           # 현재 디렉토리의 basename.
echo
echo `basename $0`          # 스크립트 이름.
echo $0                     # 스크립트 이름.
echo "${0##*/}"             # 스크립트 이름.
echo
filename=test.data
echo "${filename##*.}"      # 데이타
                            # 전체 파일이름에서 확장자.

${var%pattern}, ${var%%pattern}

$pattern$var뒷 부분과 가장 짧거나 가장 길게 일치하는 부분을 삭제.

Bash 버전 2에는 옵션이 더 늘었습니다.

예 9-12. 매개변수 치환에서의 패턴 매칭

#!/bin/bash
# 매개변수 치환 연산자인 # ## % %% 를 써서 패턴 매칭하기.

var1=abcd12345abc6789
pattern1=a*c  # * (와일드 카드)는 a 와 c 사이의 모든 문자와 일치합니다.

echo
echo "var1 = $var1"           # abcd12345abc6789
echo "var1 = ${var1}"         # abcd12345abc6789   (다른 형태)
echo "${var1} 에 들어 있는 글자수 = ${#var1}"
echo "pattern1 = $pattern1"   # a*c  ('a'와 'c' 사이의 모든 문자)
echo


echo '${var1#$pattern1}  =' "${var1#$pattern1}"    #         d12345abc6789
# 앞에서부터 가장 짧게 일치하는 3 글자를 삭제             abcd12345abc6789
# ^^^^^^^^^^                                              |-|
echo '${var1##$pattern1} =' "${var1##$pattern1}"   #                  6789      
# 앞에서부터 가장 길게 일치하는 12 글자를 삭제            abcd12345abc6789
# ^^^^^^^^^^                                              |----------|

echo; echo

pattern2=b*9            # 'b'와 '9' 사이의 모든 문자.
echo "var1 = $var1"     # abcd12345abc6789 를 계속 씁니다.
echo "pattern2 = $pattern2"
echo

echo '${var1%pattern2}  =' "${var1%$pattern2}"     #     abcd12345a
# 뒤에서부터 가장 짧게 일치하는 6 글자를 삭제            abcd12345abc6789
# ^^^^^^^^^^                                                       |----|
echo '${var1%%pattern2} =' "${var1%%$pattern2}"    #     a
# 뒤에서부터 가장 길게 일치하는 12 글자를 삭제           abcd12345abc6789
# ^^^^^^^^^^                                              |-------------|

# 이렇게 외우세요.
#           # 과 ## 은 문자열의 앞쪽에서부터 동작을 하고,
#           % 와 %% 는 뒤쪽에서부터 동작을 합니다.

echo

exit 0

예 9-13. 파일 확장자 바꾸기:

#!/bin/bash

#                 rfe
#                 ---

# 파일 확장자 바꾸기(Renaming file extensions).
#
#         rfe 원래확장자 새확장자
#
# 예제:
# 현재 디렉토리의 *.gif 를 *.jpg 로 바꾸려면,
#          rfe gif jpg

ARGS=2
E_BADARGS=65

if [ $# -ne $ARGS ]
then
  echo "사용법: `basename $0` 원래확장자 새확장자"
  exit $E_BADARGS
fi

for filename in *.$1
# 첫번째 인자로 끝나는 파일 목록을 전부 탐색.
do
  mv $filename ${filename%$1}$2
  # 첫번째 인자와 일치하는 부분을 떼어내고,
  # 두번째 인자를 덧붙임.
done

exit 0

변수 확장/문자열조각 대치

여기서 소개하는 것들은 ksh에서 따 온 것입니다.

${var:pos}

var 변수가 pos 옵셋부터 시작하도록 확장.

${var:pos:len}

변수 varpos에서 최대 len만큼의 길이를 갖도록 확장. 이 연산자의 창조적인 사용 예제를 보려면 예 A-9 참고.

${var/patt/replacement}

var에 첫번째로 일치하는 pattreplacement로 대치시킴.

replacement를 안 적으면 patt이 지워짐.

${var//patt/replacement}

전역 대치(Global replacement). var에서 일치하는 모든 pattreplacement로 대치시킴.

위의 경우와 비슷하게 replacement를 안 적어주면 모든 patt이 지워짐.

예 9-14. 임의의 문자열을 파싱하기 위해 패턴 매칭 사용하기

#!/bin/bash

var1=abcd-1234-defg
echo "var1 = $var1"

t=${var1#*-*}
echo "var1 (첫번째 - 를 포함한 부분까지 잘라냄) = $t"
#  # 이 가장 짧은 문자열과 매치하고 * 가 빈 문자열을 포함한 모든것과 
#+ 일치하기 때문에 t=${var1#*-}  도 똑같이 동작합니다.
# (S. C. 가 지적해 주었습니다.)

t=${var1##*-*}
echo "var1에 \"-\" 이 들어있다면 빈 문자열을 리턴함...    var1 = $t"


t=${var1%*-*}
echo "var1 (제일 마지막의 - 부터 끝까지 잘라냄) = $t"

echo

# -------------------------------------------
path_name=/home/bozo/ideas/thoughts.for.today
# -------------------------------------------
echo "path_name = $path_name"
t=${path_name##/*/}
echo "접두어가 잘려진 path_name = $t"
# 이 경우에서는    t=`basename $path_name` 이라고 해도 똑같습니다.
#  t=${path_name%/}; t=${t##*/}   라고 하는 것이 좀 더 일반적인 해법이지만,
#+ 가끔은 실패할 수도 있습니다.
#  $path_name 이 뉴라인으로 끝날 경우에는 `basename $path_name` 은 제대로 동작하지 않겠지만,
#+ 위 표현식은 제대로 동작할 것입니다.
# (Thanks, S.C.)

t=${path_name%/*.*}
# t=`dirname $path_name`    라고 하는 것과 똑같습니다.
echo "접미어가 잘려진 path_name = $t"
# 이렇게 하면 "../", "/foo////", # "foo/", "/" 같은 경우에는 실패할 것입니다.
#  basename 은 접미어가 없고 dirname 은 있을 경우에 접미어를 삭제하는 것
#+ 역시 복잡한 문제입니다.
# (Thanks, S.C.)

echo

t=${path_name:11}
echo "첫번째 11개 문자가 잘려나간 $path_name = $t"
t=${path_name:11:5}
echo "첫번째 11개 문자가 잘려나가고 길이가 5인 $path_name = $t"

echo

t=${path_name/bozo/clown}
echo "\"bozo\" 가 \"clown\" 으로 대치된 $path_name = $t"
t=${path_name/today/}
echo "\"today\" 가 삭제된 $path_name = $t"
t=${path_name//o/O}
echo "모든 소문자 o 를 대문자로 만든 $path_name = $t"
t=${path_name//o/}
echo "모든 소문자 o 를 삭제한 $path_name = $t"

exit 0
${var/#patt/replacement}

var접두어(prefix)가 patt과 일치하면 pattreplacement로 치환.

${var/%patt/replacement}

var접미어(suffix)가 patt과 일치하면 pattreplacement로 치환.

예 9-15. 문자열의 접두, 접미어에서 일치하는 패턴 찾기

#!/bin/bash
# 문자열의 접두어/접미어 에서 패턴 치환 하기.

v0=abc1234zip1234abc    # 원래 변수.
echo "v0 = $v0"         # abc1234zip1234abc
echo

# 문자열의 접두어(시작)에서 일치.
v1=${v0/#abc/ABCDEF}    # abc1234zip1234abc
                        # |-|
echo "v1 = $v1"         # ABCDE1234zip1234abc
                        # |---|

# 문자열의 접미어(끝)에서 일치.
v2=${v0/%abc/ABCDEF}    # abc1234zip123abc
                        #              |-|
echo "v2 = $v2"         # abc1234zip1234ABCDEF
                        #               |----|

echo

#  ----------------------------------------------------
#  문자열의 시작과 끝에서 일치가 일어나야지만
#+ 대치가 일어납니다.
#  ----------------------------------------------------
v3=${v0/#123/000}       # 일치하지만 시작 부분이 아닙니다.
echo "v3 = $v3"         # abc1234zip1234abc
                        # *대치가 안 일어납니다*.
v4=${v0/%123/000}       # 일치하지만 끝 부분이 아닙니다.
echo "v4 = $v4"         # abc1234zip1234abc
                        # *대치가 안 일어납니다*.

exit 0			
${!varprefix*}, ${!varprefix@}

이미 선언된 변수들 중에 varprefix로 시작하는 변수들.
xyz23=whatever
xyz24=

a=${!xyz*}      # 선언된 변수중 "xyz"로 시작하는 변수로 확장.
echo "a = $a"   # a = xyz23 xyz24
a=${!xyz@}      # 위와 같음.
echo "a = $a"   # a = xyz23 xyz24

# Bash 2.04 버전에서 이 기능이 추가됨.

주석

[1]

비대화형 스크립트에서 $parameter 가 널이면 127 종료 상태를 갖고 종료됩니다("command not found" 에러 코드).