본문 바로가기

BashShell Programming

6. Command-Line Options and Typed Variables


6. Command-Line Options and Typed Variables

 

지난 챕터들을 통하여 이제 어느 정도 프로그래밍 기술들에 대해 감을 잡았을 것이라 생각됩니다. 지금까지 배운 내용들을 통하여 유용한 스크립트들과 함수들을 많이 작성해낼 있으실 겁니다.

하지만 여전히 다른 일반적인 UNIX 명령어처럼 동작하는 코드들을 작성해내기에는 지식의 갭이 존재한다는 것이 느껴지실 겁니다. 만약 UNIX 경험해보신 분이라면, 지금까지 등장했던 예제 스크립트들에서는 커맨드 창에서 실행 옵션 앞에 대시(-) 집어 넣는 식의 방법을 사용하지 않았음을 눈치채셨을 것입니다. 그리고 C 파스칼과 같은 전통적인 프로그램을 사용해본 적이 있다면, 지금까지 등장했던 변수형이 여러 가지가 아니라 문자형 하나뿐이었다는 것을 눈치채셨을 것입니다. (예를 들면 아직 수학 계산을 어떻게 처리하는지 살펴보지 못했습니다)

기능상의 이러한 부분들이, 유용한 UNIX 프로그래밍 언어로서 작용할 있게끔 해주는 중요한 부분이 됩니다. 이번 챕터에서는 배시에서 이러한 기능들을 어떻게 지원하는지 살펴볼 것입니다.

6.1 Command-Line Options

스크립트나 함수가 실행 명랭행 인자를 저장하는 위치 매개변수(1, 2, 3. 이런 식으로 이름이 붙는 변수) 많은 예제에서 이미 살펴봤습니다. 또한, * (모든 인자의 문자열), #(인자의 ) 같은 변수도 살펴봤습니다. 실질적으로 변수들은 사용자의 명령행에 모든 정보를 갖고 있습니다.

일반적인 UNIX 명령행들은 command [-options]args 형태로 구성되며, 옵션은 하나도 없거나 하나 이상일 있습니다.

만약 스크립트가 teatime alice hatter 라는 명령행을 처리할 , $1 “alice” 이고, $2 “ hatter” 입니다. 그러나 teatime -o alice hatter 명령행을 처리할 때는 $1 –o 이고, $2 “ alice” 이고 $3 “ hatter” 입니다.

 

위와 같이 처리하고자 다음과 같은 코드로 작성될 것입니다.

if [ $1 = -o ]; then
code that processes the -o option
    1=$2
    2=$3
fi
normal processing of $1 and $2...

코드는 가지 문제가 있습니다. 먼저, 위치 매개변수는 읽기 전용이기 때문에 1=$2 같이 선언하는 것은 문법에 어긋납니다.

심지어 문법적으로 맞는다 해도 스크립트가 다룰 있는 인자의 개수는 한계를 두기 때문에 다른 문제가 생길 있습니다. 그러면 매우 잘못된 코드가 있습니다. 게다가 명령행에 개씩이나 옵션이 있다면 매우 지저분한 코드가 것입니다.

 

6.1.1 shift

다행히도 쉘은 문제를 shift 라는 명령을 사용하여 해결할 있습니다.

shift 명령은 다음과 같이 동작합니다.

1=$2
2=$3
...

인자의 개수는 상관 없습니다. shift 명령에 숫자 인수를 주면 숫자 인수만큼 건너 뛰게 됩니다. 예를 들어, shift 3 하게 되면 다음과 같습니다.

1=$4
2=$5
...

앞에서 작성한 코드에 하나의 옵션(-o) 인자들을 넣습니다.

if [ $1 = -o ]; then
process the -o option
    shift
fi
normal processing of arguments...

결과는 if 구문 다음에 $1, $2 등은 올바르게 인자들이 설정됩니다.

지금까지 봐왔던 간단한 옵션으로 구성된 프로그래밍 기능에 shift 명령을 함께 사용할 있습니다.  그러나 조금 복잡한 상황이 되면 추가적으로 알아야 것이 있습니다. 바로 getopts 내장 명령으로 뒤에서 설명하겠습니다.

shift 명령 가지고 챕터 4(Task 4-1) 에서 보았던 –N 옵션으로 내림차순 스크립트 작성한 것을 충분히 구현 있습니다.

스크립트를 다시 기억해 보세요. 아티스트 명과 해당 아티스트가 소유한 앨범의 개수의 목록으로 파일을 입력 받습니다. 그리고 목록으로 정렬하여 가장 많은 숫자로 내림차순으로 출력합니다.

실제 데이터를 처리하는 코드 부분은 다음과 같습니다.

filename=$1
howmany=${2:-10}
sort -nr $filename | head -$howmany

원래 스크립트에서 구문은 highest filename [-N] 입니다. 여기서 N 생략하면 10 기본값 입니다. 부분을 UNIX 문법으로 바꾸어보면 다음과 같이 있습니다.

if [ -n "$(echo $1 | grep '^-[0-9][0-9]*$')" ]; then
    howmany=$1
    shift
elif [ -n "$(echo $1 | grep '^-')" ]; then
    print 'usage: highest [-N] filename'
    exit 1
else
    howmany="-10"
fi
filename=$1
sort -nr $filename | head $howmany

$1 해당 패턴과 일치하는지 확인하기 위해 grep 유틸리티를 사용하였습니다. grep 유틸리티에 정규 표현식인 ^-[0-9][0-9]*$ 이용한 것은 처음 대시(-) 문자 다음에 숫자와 다른 숫자가 붙는 중에서 선택한다는 것으로 해석된다.

일치하는 것을 찾았을 grep 매치된 것을 돌려주고 검사는 참이 됩니다. 그렇지 않으면 grep 아무것도 반환하지 않고 elif 구문으로 진행하게 됩니다.

쉘이 ‘$’ ‘*’ 해석하지 않기 위해 정규 표현식에 싱글 쿼테이션(‘) 으로 묶어 변경되지 않고 바로 grep 으로 통과하도록 것을 주목해야 합니다.

$1 일치 하지 않았다면 옵션이 있는지를 확인해 봐야 합니다. 다른 어떤 패턴과 일치하는지 확인해 봐야 합니다. 만약 패턴과 매치하는 것이 없다면 에러 메시지를 출력하고 오류 종료 코드를 돌려 줍니다. 마지막 경우인 else 구문까지 이르면 $1 파일명으로 있으므로 다음 코드가 실행될 것입니다.

스크립트의 나머지 과정은 이전 내용을 확인하시면 됩니다.

지금까지 배웠던 것을 일반적인 복합적인 옵션의 처리에 관련 시킬 있습니다.

명확히 하기 위해 스크립트 명을 alice 하고 –a, -b, -c 옵션들을 처리한다고 가정합니다.

while [ -n "$(echo $1 | grep '-')" ]; do
    case $1 in
        -a ) process option -a ;;
        -b ) process option -b ;;
        -c ) process option -c ;;
        *  ) echo 'usage: alice [-a] [-b] [-c] args...'
             exit 1
esac
    shift
done
normal processing of arguments...

코드는 $1 대시 문자(-) 시작할 때까지 되풀이하여 체크합니다. 다음에 case 구문은 $1 옵션에 따라 해당하는 코드를 실행합니다.

유효하지 않은 옵션이라면 , 대시 문자로 시작하는 것이 –a, -b, -c 아니라면 스크립트는 사용법을 메시지 출력하고 오류 종료 코드를 돌려주며 종료됩니다.

옵션이 처리된 다음 인자는 shift 연산이 수행됩니다. 그런 다음 결과 위치 매개 변수는 while loop 구문이 끝날 때까지 실제 인자로 설정됩니다.

코드는 하나의 문자만 처리하는 것이 아니라 옵션의 길이 상관 없이 해당 옵션을 처리할 있다는 것에 주목하기 바랍니다. 예를 들어 -a 옵션 대신에 -adventure 처리할 있다는 것입니다.

6.1.2 Options with Arguments

옵션 처리를 하는 하나 이상의 구성 요소가 필요한 것은 매우 유용합니다.

많은 명령 행들이 옵션이 가지고 있는 인자들도 함께 처리합니다.

예를 들어, 챕터 4에서 깊이 있게 다룬 cut 명령은 –d 옵션을 주면 필드로 구분하는(기본값인 TAB 아닌 경우) 인자와 함께 사용할 있습니다.

이런 옵션의 경우를 처리하라면 다른 shift 이용해야 합니다.

alice 스크립트에 –b 옵션에 자체 인자가 필요하다고 가정해 봅시다.

여기 이를 처리하도록 수정된 코드입니다.

while [ -n "$(echo $1 | grep '-')" ]; do
    case $1 in
        -a ) process option -a ;;
        -b ) process option -b
$2 is the option's argument
             shift ;;
        -c ) process option -c ;;
        *  ) echo 'usage: alice [-a] [-b barg] [-c] args...'
             exit 1
    esac
    shift
done
normal processing of arguments...

6.1.3 getopts

지금까지 명령행 옵션 처리하는 방법에 대해 부족하지만 알아보았습니다. 앞에서의 코드는 싱글 대시 문자(-) 가진 인자가 결합된 것을 허용하지 않았습니다. 예를 들어, -a –b –c 대신에 –abc 라고 하는 것을 말합니다. 또한 옵션에 특정 인자를 사이에 공백 없이 사용하는 것을 허용하지 않았습니다. 예를 들어, 옵션에 인자를 추가할 -b arg 대신 –barg 라고 하면 처리하지 못했습니다.*

* 이런 명령은 UNIX 명령에서는 대부분 허용되지만 사용자 매뉴얼에서 표준 명령 문법에는 어긋나는 것이라 있습니다.

쉘은 이런 제약 사항 없이 복합 옵션을 다루는 내장된 방법을 제공합니다.

getopts** 내장 명령은 옵션 처리 루프에서 while 조건으로 사용될 있습니다.

**getopts 외부 명령인 getopt 대신합니다. getopts 프로그래밍에서 사용됩니다. getopts getopt 보다 문법에 통합되어 있고 실행 면에서도 효율적입니다. C 프로그래머들은 getopts 표준 라이브러리 루틴인 getopt 매우 유사하다고 인식합니다.

어떤 옵션이 유효한지, 어떤 옵션이 인자를 필요로 하는지 세부사항을 주면 옵션을 차례로 처리하는 루프를 설정할 수도 있을 것입니다.

getopts 2개의 인자가 필요합니다.

번째 인자는 문자와 콜론(:) 포함한 문자열입니다.

문자는 유효한 옵션입니다. 문자 다음에 따라오는 콜론(:) 인자가 필요하다는 것입니다. getopts 명령행에서 옵션을 제거하면 대시 문자(-) 시작하지 않는 옵션을 getopts 번째 인자로 이름의 변수에 할당합니다.

처리 해야 옵션이 계속 남아 있으면, getopts 종료 코드 0 반환합니다. 옵션을 모두 처리하면 종료 코드 1 반환하고 while 루프가 종료 됩니다.

getopts 밖에도 옵션 처리를 쉽게 만드는 다른 일들을 합니다. 예제에서 getopts 어떻게 사용하는지를 살펴보면 알게 것입니다.

while getopts ":ab:c" opt; do
    case $opt in
        a  ) process option -a ;;
        b  ) process option -b
$OPTARG is the option's argument ;;
        c  ) process option -c ;;
        \? ) echo 'usage: alice [-a] [-b barg] [-c] args...'
             exit 1
    esac
done
shift $(($OPTIND - 1))
normal processing of arguments...

while 조건에서 getopts 호출하면 –a, -b, -c 옵션을 받을 있게 설정되며 –b 옵션에는 인자를 명시할 있게 됩니다. (콜론(:) 으로 시작하는 옵션에 대해서는 잠시 후에 설명하겠습니다.)

루프가 실행될 때마다 opt 변수에 대시 문자(-) 없이 사용 가능한 가장 마지막 옵션이 들어갑니다.

사용자가 잘못된 옵션을 입력하게 되면, getopts 정상적으로 오류 메시지를 출력(이런 형태로 cmd: getopts: illegal option — o) 하고 opt 변수에 ‘?’ 넣습니다.

그러나 콜론(:) 으로 시작하는 문자의 옵션으로 시작할 때는 getopts 이런 메시지를 출력하지 않습니다.***

***getopts 메시지를 출력하지 않게 하려면 OPTERR 환경 변수를 0으로 설정합니다. 여기에서는 계속해서 콜론(:) 사용할 것입니다.

위에서처럼 ‘?’ 다루는 경우에 오류 메시지 제공하기 때문에 콜론(:) 지정하는 것을 권장합니다.

getopts 하고자 하는 것을 반영하기 위해 코드에서 case 구문을 수정했습니다.

그러나 while 루프 안에 shift 구문이 이상 있지 않다는 것에 주목하기 바랍니다.

getopts shift 연연하지 않고 자기 맡은 임무를 수행 합니다. (?)

getopts does not rely on shifts to keep track of where it is.

getopts 끝날 때까지, 다시 말해 while 루프가 종료될 때까지 shift 명령은 인자를 처리하는 것은 불필요합니다.

옵션에 인자가 있다면, getopts OPTARG 변수에 저장하여 코드에서 옵션 처리하는 것으로 사용될 있습니다.

shift 구문은 while 루프 다음에 씁니다.

getopts 다음에 처리될 인자의 수를 OPTIND 변수에 저장합니다. 이런 경우에는 번째(옵션 아닌) 명령행 인자의 개수가 됩니다. 예를 들어, alice -ab rabbit 명령행이라면 $OPTIND 3 됩니다. 명령행이 alice -a –b rabbit 이라면 $OPTIND 4 됩니다.

$(($OPTIND - 1)) 표현은 $OPTIND – 1 같은 산술 표현식입니다. ( 부분은 다음 챕터에서 다시 보게 것입니다.)

값은 shift 인자로 사용됩니다.

결과 정확한 인자의 개수는 shift 처리가 끝난 뒤에 진짜 인자로 $1, $2 등이 남게 됩니다.

계속 하기 전에 getopts 모든 것을 요약하는 시간을 갖겠습니다.

1. 번째 인자는 유효한 옵션을 모두 포함하는 문자열입니다. 옵션에 인자가 필요하다면, 문자열에서 콜론(:) 문자 뒤에 따라옵니다. 콜론(:) 먼저 오면 getopts 사용자가 잘못된 옵션을 넣었을 오류 메시지를 출력하지 않게 됩니다.

2. 번째 인자는 현재 처리된 옵션 문자(대시문자(-) 없이) 변수의 이름입니다.

3. 옵션에 인자를 받으면, 인자는 OPTARG 변수에 저장됩니다.

4. OPTIND 변수에는 다음 명령행에서 처리될 인자의 개수가 포함됩니다. getopts 끝나면, 처음 진짜 인자의 수와 같게 됩니다.

getopts 장점은 추가되는 코드를 최소화 있는 것과 표준 UNIX 옵션 문법(사용자 매뉴얼에 명시되어 있는) 모두 지원하는 처리할 있다는 것입니다.

구체적인 예를 들어보겠습니다. 다시 그래픽 유틸리티를 처리 하는 스크립트를 봅시다. (Task 4-2) 지금까지 PCX 파일(.pcx), GIF 파일(.gif), XPM 파일(.xpm) 등과 같은 그래픽 파일의 다양한 종류를 처리할 있는 스크립트를 작성했습니다. 여기 작성했던 코드를 보며 다시 한번 상기시켜보자.

filename=$1
if [ -z $filename ]; then
    echo "procfile: No file specified"
    exit 1
fi
for filename in "$@"; do
    pnmfile=${filename%.*}.ppm
    case $filename in
        *.jpg ) exit 0 ;;
        *.tga ) tgatoppm $filename > $pnmfile ;;
        *.xpm ) xpmtoppm $filename > $pnmfile ;;
*.pcx ) pcxtoppm $filename > $pnmfile ;;
        *.tif ) tifftopnm $filename > $pnmfile ;;
        *.gif ) giftopnm $filename > $pnmfile ;;
            * ) echo "procfile: $filename is an unknown graphics file."
                exit 1 ;;
    esac
    outfile=${pnmfile%.ppm}.new.jpg
    pnmtojpeg $pnmfile > $outfile
    rm $pnmfile
done

다양한 다른 그래픽 파일을 페이지에 알맞은 JPEG 파일로 변환하기 위한 스크립트로 상당히 좋습니다.

그러나 NetPBM 이미지 파일을 사용할 있는 파일 변환뿐만 아니라 다양하게 유용한 유틸리티입니다. 스크립트에서 선택할 있게 하면 좋을 것입니다.

사이즈를 변경하고나 테두리를 만드는 이런 것들을 포함하는 이미지 수정 작업을 있도록 하는 것을 원합니다. 가능한 유연하게 스크립트를 만들도록 원합니다.

이미지 사이즈 변경을 하거나 그림의 테두리를 빼고 싶을 스크립트를 가지고 작업을 있도록 해야 합니다. 이는 명령행 옵션 처리를 편리하게 사용하고자 합니다.

NetPBM 유틸리티인 pnmscale 이용해서 이미지 사이즈 변경 있습니다.

NetPBM 패키지인 자체 형식인 PNM (Portable Anymap) 있다고 지난 챕터 것을 상기 시켜보세요.

PNM 가지고 테두리를 추가하거나 사이즈 변경하는 것을 있습니다.

다행히도, 이미 스크립트에는 다양한 그래픽 파일 형식을 PNM으로 변환할 있습니다. 뿐만 아니라 PNM 파일은, pnmscale 유틸리티에 이미지 크기를 어떻게 것인지 인자를 필요로 합니다.

NetPBM 유틸리티로부터 진단 정보를 출력하지 않게 하는 -quiet 옵션도 필요합니다.

다양한 방법이 있을 있지만 –xysize 가로, 세로의 픽셀 크기를 선택할 있게 합니다.

색깔이 있는 테두리 입히는 pnmmargin 라는 다른 유틸리티도 필요합니다.

테두리의 색깔과 테두리 너비를 인자로 받습니다.

그래픽 유틸리티에는 가지 옵션이 필요합니다.

-s size 최종 이미지의 크기(테두리 너비 제외) 지정하고, -w width 이미지의 테두리 너비를 지정하고 –c color 테두리의 색상을 지정합니다.

다음은 옵션 처리를 포함하는 procimage 스크립트 코드 입니다.

# Set up the defaults
size=320
width=1
colour="-color black"
usage="Usage: $0 [-s N] [-w N] [-c S] imagefile..."
while getopts ":s:w:c:" opt; do
    case $opt in
      s  ) size=$OPTARG ;;
      w  ) width=$OPTARG ;;
      c  ) colour="-color $OPTARG" ;;
      \? ) echo $usage
           exit 1 ;;
    esac
done
shift $(($OPTIND - 1))
if [ -z "$@" ]; then
    echo $usage
    exit 1
fi
# Process the input files
for filename in "$@"; do
    ppmfile=${filename%.*}.ppm
    case $filename in
        *.gif ) giftopnm $filename > $ppmfile ;;
        *.tga ) tgatoppm $filename > $ppmfile ;;
        *.xpm ) xpmtoppm $filename > $ppmfile ;;
        *.pcx ) pcxtoppm $filename > $ppmfile ;;
        *.tif ) tifftopnm $filename > $ppmfile ;;
        *.jpg ) jpegtopnm -quiet $filename > $ppmfile ;;
            * ) echo "$0: Unknown filetype '${filename##*.}'"
                exit 1;;
    esac
    outfile=${ppmfile%.ppm}.new.jpg
    pnmscale -quiet -xysize $size $size $ppmfile |
        pnmmargin $colour $width |
        pnmtojpeg > $outfile
    rm $ppmfile
done

먼저 스크립트의 줄은 기본 값으로 변수 값이 초기화 되어 있습니다.

기본 값은 이미지 크기는 320 픽셀, 검은색 테두리의 너비는 1 픽셀로 설정되어 있습니다.

while, getopts, case 구문은 이전에서와 같은 방법으로 옵션을 처리하였습니다.

처음 3가지 옵션에 해당하는 코드는 인자를 변수 (기본값 대신) 할당하는 부분입니다. 마지막 옵션은 잘못된 옵션을 위해 있습니다.

코드의 나머지는 이전에서와 대체로 똑같은 방법으로 처리합니다.

제외된 부분은 파이프라인으로 처리된 pnmscale pnmmargin 유틸리티를 추가한 것입니다.

스크립트는 다른 파일명을 만들어 냅니다. 기존 파일명에 .new.jpg 붙게 됩니다.

이는 원본 파일의 손상 없이 크기나 테두리를 추가하여 JPEG 파일로 처리하도록 하기 위해서 입니다.

스크립트는 크기 변환을 원치 않을 어떻게 작동할지에 대해서는 논하지 않았습니다. 다음 챕터에서 조금 발전된 스크립트를 보게 것입니다.

 

6.2 Typed Variables

지금까지 봐왔던 배시 변수는 문자 값으로 선언된 것이었습니다. 변수는 읽기 전용이나 정수 타입과 같은 다른 속성도 있습니다.

내장 명령인 declare 이용해서 변수 속성을 설정할 있습니다.*

* declare 동의어인 typeset 이라는 내장 명령이 있지만 구식입니다.

6-1 declare 함께 쓰는 옵션을 정리해두었습니다.**

** -a –F 옵션은 배시 2.0 이전에서는 사용할 없습니다.

- 옵션을 켜고, + 옵션을 끕니다.

6-1. declare 옵션

Option

Meaning

-a

변수는 배열로 취급

-f

함수명만 사용

-F

정의되지 않은 함수명만 출력

-i

변수는 정수로 취급

-r

변수를 읽기 전용으로 표시

-x

export를 통해 환경변수로 표시

 

declare 입력하면 모든 변수 값이 출력 됩니다. -f 옵션은 현재 환경에서 함수명과 정의만을 출력합니다. –F 옵션은 함수명만 출력합니다.

-a 옵션은 배열을 선언하는 사용됩니다. 배열 변수를 아직 보지 않았지만 조만간 다루게 입니다.

-i 옵션은 정수 변수를 정의하는데 사용됩니다. 정수 변수는 숫자 값을 가지며 산술 연산에 의해 변경될 수도 있습니다. 다음 예제를 보겠습니다.

$ val1=12 val2=5
$ result1=val*val2
$ echo $result1
val1*val2
$
$ declare -i val3=12 val4=5
$ declare -i result2
$ result2=val3*val4
$ echo $result2
60

번째 예제의 변수는 일반적인 변수이고 결과값도 “val1*val2” 이라는 문자열입니다.

번째 예제는 모든 변수가 정수 타입으로 선언되어 있습니다.

변수 값의 결과는 12 곱하기 5 산술 계산한 결과, 60 입니다.

실제로 val3 val4 정수 타입으로 선언할 필요는 굳이 없습니다.

result2 할당된 모든 것은 산술 구문으로 해석되어 계산되었습니다.

declare -x 옵션은 챕터 3에서 보았던 내장 함수인 export 같은 방법으로 작동 됩니다. 현재 환경 밖으로 변수들을 내보낼 있습니다.

-r 옵션은 읽기 전용 변수를 정의하는데 사용됩니다. 읽기 전용 변수는 다음에 할당 구문으로 값을 변경하는 것을 하지 못합니다.

비슷한 내장 명령으로 readonly name ... declare –r 같은 방법으로 동작합니다.

readonly 가지 옵션이 있습니다. –f 옵션은 변수명 대신에 함수명으로서 인자를 해석하게 됩니다. –p 옵션은 읽기 전용으로 모든 항목을 출력합니다. –a 옵션은 배열로 인자를 해석합니다.

마지막으로 함수에서 declare 정의된 변수는 함수 내에서 사용하도록 , local 선언된 것과 같습니다.

6.3 Integer Variables and Arithmetic(정수형 변수와 산술 연산)

지난 그래픽 유틸리티 예제에서 $(($OPTIND - 1)) 표현은 정수 연산으로 있는 다른 방법으로 있습니다.

추측하기로 쉘은 $((  and )) 둘러싸인 문자열도 산술 표현 식으로 해석됩니다.*

* $[...] 오래된 형식이지만 사용할 수도 있으나 배시 다음 버전에서 빠질 예정이기 때문에 권장하지는 않습니다.

산술연산 표현식에서 변수는 $ 기호를 앞에 필요는 없지만 그렇게 사용하는 것은 옳지 않습니다.

산술 표현식은 틸드 문자(~), 변수, 명령행 대체와 같이 따옴표(“ “) 안에서 계산됩니다.

마침내 따옴표 문자에 대해 정의된 규칙들의 상태를 알아보겠습니다. 틸드 문자나 달러($) 문자를 포함한 표현식인 경우엔 큰따옴표 (“ “) 사용하고 경우를 제외하고는 의심스럽다면 따옴표 (‘ ’) 문자를 둘러싸면 된다.

예를 들어 UNIX 최신 버전에서 날짜 명령은 출력을 어떤 형태로 할지 정하는 인자가 필요합니다.

+%j 인자는 오늘이 인지를 표시해줍니다. , 작년 12 31 이후부터 일인지를 출력합니다.

+%j 인자를 사용하면 휴일의 예측 메시지를 출력할 있습니다.

echo "Only $(( (365-$(date +%j)) / 7 )) weeks until the New Year"

챕터 7에서 명령행 처리의 전체 계획에서 보게 것입니다.

We’ll  show  where  this  fits  in  the  overall  scheme  of  command-line  processing  in Chapter 7.

산술연산 표현식의 특징은 배시 문법으로 내장되어 있지만 expr 외부 명령 통해서 쉘에서도 사용 가능합니다.

이것은 쉘에 외부 명령으로 제공되었던 뛰어난 기능이 통합되어 낫게 다른 예제입니다. 이미 살펴본 getopts 또한 이런 경향을 반영한 예입니다.

배시의 산술연산 표현식은 Java C언어에서 동일하게 사용됩니다.**

** 연산자들의 할당 구문은 문법에 맞습니다. 예를 들어, $((x += 2)) x 2 더한 값을 x 다시 저장합니다.

우선 순위와 결합 방식도 C언어에서와 동일합니다.

6-2에서는 배시에서 지원하는 산술연산자를 정리하였습니다.

특수 문자가 포함되어 있는 것들이 있지만 \ 문자(역슬래시 이스케이프) 필요는 없습니다. $((...)) 안에 들어가기 때문입니다.

6-2. 산술 연산자

Operator

Meaning

++

증가 연산자 (ex> a++, ++a)

--

감소 연산자 (ex> a--, --a)

+

더하기

-

빼기

*

곱하기

/

나누기 (소수점 이하 버림)

%

나머지

**

거듭제곱

<< 

좌측 Shift 연산자 (왼쪽으로 비트 이동)

>> 

우측 Shift 연산자 (오른쪽으로 비트 이동)

&

비트 연산의 and

|

비트 연산의 or

~

비트 연산의 not

!

논리 부정

^

비트 연산의 exclusive or

,

순차 계산

++ 연산자는 변수 하나를 증가 혹은 감소하고자 유용합니다.*

* ++ 연산자는 배시 2.04이전에서는 사용하지 못합니다.

Java C언어에서 또한 동일하게 사용됩니다. value++ 값은 1입니다.

후증가연산자 라고 불립니다. ++value 전증가연산자 입니다. 차이점은 예제를 통해 확연히 나타납니다.

$ i=0
$ echo $i
0
$ echo $((i++))
0
$ echo $i
1
$ echo $((++i))
2
$ echo $i
2

가지 경우 모두 값은 하나씩 증가하였습니다.

그러나 번째 경우(후증가연산자) 변수 값이 출력된 다음에 증가되었습니다.

번째 경우(전증가연산자) 변수 값이 증가를 수행한 다음에 출력되었습니다.

However,  in  the  first  case (post-increment) the value of the variable was passed to echo and then the variable was incremented.

In the second case (pre-increment) the increment was performed and then the variable passed to echo.

삽입어구는 그룹 하위식으로 사용할 있습니다.

Parentheses can be used to group subexpressions.

산술 표현식 문법은 C언어에서도 동일하게 진릿값으로 1이면 , 0이면 거짓으로 관계 연산자를 지원합니다.

6-3 에서 관계 연산자와 함께 결합하여 사용할 있는 논리 연산자를 정리하였습니다.

6-3. Relational operators

Operator

Meaning

< 

작다

> 

크다

<=

작거나 같다

>=

크거나 같다

==

같다

!=

같지 않다

&&

논리 and

||

논리 or

예를 들어, $((3 > 2)) 값은 1입니다. $(( (3 > 2) || (4 <= 1) )) 또한 값은 1입니다. 적어도 개의 하위식 중에서 적어도 하나만 참이면 참입니다.

쉘은 N 진법 또한 지원하며, 여기서 N 2부터 36까지 입니다.

표기법은 B#N으로 “B 진법으로 N”라는 것입니다. 물론 N 기본값이 10이기 때문에 B#처럼 생략할 있습니다.

6.3.1 Arithmetic Conditionals (산술 연산 조건들)

챕터 5에서 [...] 표기법을 사용해서 문자열을 어떻게 비교하는지 살펴봤습니다.

산술 조건 또한 이것과 같은 방법으로 검사할 있습니다.

그러나 검사는 자체 연산자를 가지고 수행할 있습니다.

내용을 6-4 정리하였습니다.

6-4. 검사 관계 연산자

Operator

Meaning

-lt

작다

-gt

크다

-le

작거나 같다

-ge

크거나 같다

-eq

같다

-ne

같지 않다

문자열을 비교함으로써 산술연산 검사는 혹은 거짓을 결과로 반환합니다. 0 이면 , 1이면 거짓입니다.

예를 들어 [ 3 -gt 2 ] 수행하면 종료 코드는 0입니다. [ \( 3 -gt 2 \) || \( 4 -le 1 \) ] 종료 코드는 0입니다. 그러나 [ \( 3 -gt 2 \) && \( 4 -le 1 \) ] 번째 하위식이 참이 아니기 때문에 종료 코드가 1입니다.

예제에서는 괄호를 이스케이프 하여 별개의 인자로 테스트를 하도록 넘겨주면 됩니다.

In these examples we have had to escape the parentheses and pass them to test  as separate arguments.

보시면 알겠지만 많은 괄호가 있으면 읽어내기가 매우 어렵습니다.

다른 방법은 $((...)) 구문을 사용해서 조건을 묶어 산술 연산 검사를 합니다.

예를 들어, [ $(((3 > 2) && (4 <= 1))) = 1 ] 조건부를 계산하여 결과를 1() 비교하는 것입니다.

((...)) 구문을 사용함으로써 산술 연산 검사를 수행하는 깔끔하고 효율적인 구조입니다.

경우에는 표현식이 참이면 종료 코드가 0이고, 아니면 1입니다.

위의 표현식을 ((...)) 구문을 사용하면 (( (3 > 2) && (4 <= 1) )) 이렇게 됩니다.

예제는 전에도 말했듯이 번째 하위식이 거짓이기 때문에 종료 코드로 1 반환합니다.

6.3.2 Arithmetic Variables and Assignment (산술 연산 변수와 할당)

앞에서 바와 같이 declare 정수형 변수를 정의할 있습니다.

산술 연산 구문을 계산하고 let 이용하여 변수에 할당할 수도 있습니다.

구문은 다음과 같습니다.

let intvar=expression

let 구문에서는 $((  and )) 함께 표현식을 둘러쌀 필요가 없습니다. (이미 중복이기 때문에)

let 정수형 타입의 변수를 생성하지 않습니다. 단지 표현식이 산술 연산으로 해석되어 할당되기 때문입니다.

여느 변수 할당하는 것과 마찬가지로 등호 기호(=) 양쪽에 공백이 있으면 안됩니다.

쉘이 많은 문자를 특수하게 다룰 있기 때문에 따옴표로 표현식을 둘러싸는 것이 가장 좋은 방법입니다. ( 예를 들면, *, #, 괄호 )

게다가 공백이나 TAB 포함하는 표현식이라면 따옴표로 묶어줘야 합니다.

6-5 예제를 살펴보겠습니다.

 

 

6-5. 정수 표현식 할당의 예제

Assignment

Value

let x=

$x

1+4

5

1 + 4’

5

(2+3) * 5’

25

2 + 3 * 5’

17

17 / 3’

5

17 % 3’

2

1<<4’

16

48>>3’

6

17 & 3’

1

17 | 3’

19

17 ^ 3’

19

Task 6-1

 

여기 정수형 산술 연산을 이용하는 작은 과제가 있습니다.

어떤 디렉터리를 인자(하위 디렉터리 포함) 받아 디스크 사용량을 요약해서 출력하는 ndu 라고 불리는 스크립트를 작성합니다. [ 단위는 바이트, 킬로바이트, 메가바이트 중에서 적절한 가지로]

 

 

 

 

 

다음에 해당하는 코드입니다.

for dir in ${*:-.}; do
    if [ -e $dir ]; then
        result=$(du -s $dir | cut -f 1)
        let total=$result*1024
        echo -n "Total for $dir = $total bytes"
        if [ $total -ge 1048576 ]; then
              echo " ($((total/1048576)) Mb)"
        elif [ $total -ge 1024 ]; then
              echo " ($((total/1024)) Kb)"
        fi
    fi
done

파일, 디렉터리의 디스크 사용량을 알아보기 위해 UNIX 유틸리티 du 사용할 있습니다.

du 기본 출력은 디렉터리 목록과 디렉터리가 사용한 디스크 공간입니다.

다음과 같은 것입니다.

 

6       ./toc
3       ./figlist
6       ./tablist
1       ./exlist
1       ./index/idx
22      ./index
39      .

du 디렉터리를 명시하지 않았다면 현재 디렉터리(.) 사용할 것입니다.

디렉터리와 하위 디렉터리는 해당 사용량과 함께 목록으로 출력됩니다.

전체 합계는 마지막 줄에 표시됩니다.

디렉터리와 안에 있는 모든 파일들의 디스크 사용량은 블록 단위로 표시됩니다.

운용하고 있는 유닉스 시스템에 따라 블록이 512 바이트나 1024 바이트로 표현될 잇습니다.

각각의 파일과 디렉터리는 최소한 하나의 블록을 사용합니다.

파일이나 디렉터리가 비어 있더라도 파일시스템에서 블록 하나를 점유하고 있습니다.

경우에는 전체 사용량에만 관심이 있으므로 du 출력물 마지막 행만 있으면 됩니다.

마지막 행만 얻기 위해 du –s 옵션을 사용할 있습니다.

행을 수행하면 디렉터리 명을 없애고 블록의 개수만 있습니다.

여기에서 번째 필드를 추출하기 위해 cut 명령을 사용할 있습니다.

전체를 구한 다음에 블록( 경우 1024) 바이트 수를 곱해주고 바이트 단위로 결과를 출력합니다.

전체 사용량이 1 메가바이트(1048576 바이트) 보다 큰지 검사하여 전체 사용량 값을 나눠서 메가바이트로 출력합니다.

그것이 아니고 킬로바이트 단위로 출력이 가능하면 하고 그것도 아니면 아무것도 출력하지 않습니다.

명시한 디렉터리가 반드시 존재해야 합니다. 그렇지 않으면 du 오류 메시지를 출력하며 스크립트도 실패하게 됩니다.

du 호출하기 전에 챕터 5에서 보았던 파일 검사나 디렉터리의 존재 여부 검사(-e) 수행합니다.

스크립트를 완성하기 위해 여러 인자를 제공해서 가능한 du 가깝게 구현하는 것이 좋습니다. 이는 for 루프를 코드에 사용하는 것이 좋습니다.
어떠한 인자도 입력하지 않았을 현재 디렉터리를 명시하여 사용하려고 대체 매개변수를 어떻게 사용하는지 살펴봐야 것입니다.

정수형 산술 연산의 복잡한 예제로 Task 4-8에서 pushd popd 함수에 대해 에뮬레이션을 진행해 나가겠습니다.

함수는 공백으로 분리된 디렉터리 명을 포함한 문자열로서 표현된 디렉터리 스택인 DIR_STACK 으로 작동한다는 것을 기억하십시오.

배시의 pushd popd 다음과 같은 추가된 타입의 인자를 받습니다.

pushd +n 스택에서 0부터 시작해서 n 번째 디렉터리를 받습니다. 받은 것을 위로 올리고 cd 명령을 수행합니다.

인자가 없는 pushd 스택에 있는 상위 디렉터리 개의 위치를 바꿔 위에 바뀐 디렉터리로 cd 명령을 수행합니다.

  popd +n 스택에서 n 번째 디렉터리를 받아 그냥 그것을 삭제합니다.

중에서 가장 유용한 특징은 스택에서 n 번째 디렉터리를 받는 능력입니다.

여기 함수의 가장 최신 버전입니다.

.ps 8
pushd ()
{
    dirname=$1   if [ -n $dirname ] && [ \( -d $dirname \) -a
           \( -x $dirname \) ]; then
        DIR_STACK="$dirname ${DIR_STACK:-$PWD' '}"
        cd $dirname
        echo "$DIR_STACK"
    else
        echo "still in $PWD."
    fi
}
popd ()
{
    if [ -n "$DIR_STACK" ]; then
        DIR_STACK=${DIR_STACK#* }
        cd ${DIR_STACK%% *}
        echo "$PWD"
else
        echo "stack empty, still in $PWD."
    fi
}

n 번째 디렉터리를 얻기 위해 위의 디렉터리를 스택의 임시 복사 위치로 n 반복하여 옮기는 while 루프를 이용합니다.

다음과 같이 getNdirs 함수에 loop 넣습니다.

getNdirs ()
{
    stackfront=''
    let count=0
    while [ $count -le $1 ]; do
        target=${DIR_STACK%${DIR_STACK#* }}
        stackfront="$stackfront$target"
        DIR_STACK=${DIR_STACK#$target}
        let count=count+1
    done
    stackfront=${stackfront%$target}
}

getNdirs 넘겨진 인자는 바로 n 입니다.

target 변수는 DIR_STACK 에서 임시 스택 위치인 stackfront 이동 중인 현재 디렉터리가 들어 있습니다.

target 에는 n 번째 디렉터리가 들어가게 되고, stackfront 에는 loop 끝마쳤을 target 위에 모든 디렉터리를 가지고 있을 것입니다.

stackfront null 시작하고 loop 반복 횟수를 나타내는 count 0으로 시작합니다.

loop 번째 행은 스택의 번째 디렉터리가 target 으로 복사가 됩니다.

다음 행은 target stackfront 추가되고, 다음 행에서 ${DIR_STACK#$target} 스택에서 target 삭제가 됩니다.

마지막 행은 다음 반복을 하기 위해서 카운터를 증가시킵니다.

전체 loop 문은 n+1 동안 반복하여 수행합니다. count 값이 0에서 N까지 됩니다.

loop 끝나면 $target 들어 있는 디렉터리는 n 번째 디렉터리 입니다.

${stackfront%$target} 구문은 stackfront 로부터 디렉터리를 삭제해서 stackfront 처음 n-1 디렉터리가 포함되게 됩니다.

뿐만 아니라 DIR_STACK 스택의 포함합니다. , 스택에는 처음 n 디렉터리 없이 들어가게 됩니다.

Furthermore, DIR_STACK now contains the “back” of the stack, i.e., the stack without the first n directories.

이러한 점은 pushd popd 향상된 버전으로 코드를 있습니다.

pushd ()
{
    if [ $(echo $1 | grep '^+[0-9][0-9]*$') ]; then
        # case of pushd +n: rotate n-th directory to top
        let num=${1#+}
        getNdirs $num
        DIR_STACK="$target$stackfront$DIR_STACK"
        cd $target
        echo "$DIR_STACK"
elif [ -z "$1" ]; then
        # case of pushd without args; swap top two directories
        firstdir=${DIR_STACK%% *}
        DIR_STACK=${DIR_STACK#* }
        seconddir=${DIR_STACK%% *}
        DIR_STACK=${DIR_STACK#* }
        DIR_STACK="$seconddir $firstdir $DIR_STACK"
        cd $seconddir
    else
        # normal case of pushd dirname
        dirname=$1
        if [ \( -d $dirname \) -a \( -x $dirname \) ]; then
            DIR_STACK="$dirname ${DIR_STACK:-$PWD" "}"
            cd $dirname
            echo "$DIR_STACK"
        else
            echo still in "$PWD."
        fi
    fi
}
popd ()
{
    if [ $(echo $1 | grep '^+[0-9][0-9]*$') ]; then
        # case of popd +n: delete n-th directory from stack
        let num=${1#+}
        getNdirs $num
        DIR_STACK="$stackfront$DIR_STACK"
        cd ${DIR_STACK%% *}
        echo "$PWD"
    else
        # normal case of popd without argument
        if [ -n "$DIR_STACK" ]; then
            DIR_STACK=${DIR_STACK#* }
            cd ${DIR_STACK%% *}
            echo "$PWD"
        else
            echo "stack empty, still in $PWD."
        fi
    fi
}    

함수들이 상당히 커졌습니다.  차례차례 살펴보도록 하겠습니다.

pushd 처음에 나오는 if 번째 인자가 +N 형식의 옵션인지 아닌지 검사하는 것입니다. 만약 그렇다면 번째 코드 부분이 진행됩니다.

번째 let 간단히 인자에서 더하기 기호(+) 빼버리고 정수 형으로 결과를 num 변수에 할당합니다.

이것은 차례로 getNdirs 함수에 전달됩니다.

다음 할당 구문은 DIR_STACK 새로운 목록으로 설정합니다.

그리고 나면 함수는 새로운 디렉터리로 cd 명령을 수행하고 현재 디렉터리 스택을 출력합니다.

elif 구문은 인자가 없는지 검사합니다. pushd 스택에서 상위 개의 디렉터리를 바꿉니다.

구문에서 처음 행은 상위 개의 디렉터리를 firstdir seconddir 할당하고 스택으로부터 삭제합니다.

다음에 스택을 새로운 순서로 다시 넣고 새롭게 상위 디렉터리에 cd 명령을 수행합니다.

else 구문은 일반적인 경우, 사용자가 인자로서 디렉터리 명을 입력했을 때에 해당됩니다.

popd 유사하게 동작합니다.

if 절은 +N 옵션을 검사합니다. 경우에는 n 번째 디렉터리를 삭제한다는 의미입니다.

let N 정수로 추출합니다. getNdirs 함수는 처음 n 디렉터리를 stackfront 넣습니다.

마지막으로 stack n 번째 디렉터리가 없이 다시 만들어지고, 목록에서 삭제된 디렉터리가 처음이 되면 cd 명령을 수행합니다.

else 구문은 사용자가 인자를 입력하지 않았을 일반적인 경우에 해당합니다.

주제를 마치기 전에 코드를 얼마나 이해했는지 검사하기 위해 가지 연습을 해봅시다.

1. 배시의 dirs 명령과 +n, -l 옵션을 구현해보세요. dirs 명령은 dirs 입력하여도 현재 기억된 디렉터리의 목록이 출력됩니다(스택에 있는 것들만). +n 옵션은 n 번째 디렉터리(0부터 시작) 출력하고, -l 옵션은 틸트 문자(~) 대신하여 전체 경로명으로 형태로 출력합니다.

2. getNdirs 함수를 수정하여서 스택에서 N 초과하는 디렉터리 수를 검사하고 적절한 오류 메시지를 출력하고 종료해봅니다.

3. pushd, popd, getNdirs 수정하여서 산술 연산 표현식에서 정수형 타입의 변수를 사용하도록 해봅니다.

4. getNdirs 변경하여 while loop 대신하여 cut 명령(명령어 대체) 사용하여 처음 N 디렉터리를 추출하도록 해봅시다. 이것은 코드는 적어지지만 추가의 프로세스를 생성하기 때문에 느리게 동작할 수도 있습니다.

5. pushd popd 배시 버전도 –N 옵션을 사용할 있습니다. 경우 모두 –N 절은 목록의 오른쪽에서 n 번째 디렉터리로 수행하게 됩니다. 여기서 +N 0부터 시작합니다. 기능을 추가해 봅시다.

6. getNdirs 이용하여 지난 장에서 구현했던 selectd 함수를 다시 구현해봅시다.

 

 

 

6.3.3 Arithmetic for Loops

5장에서 소개된 for loop 간단히 언급한 다른 타입의 for loop Java C 언어와 같은 많은 프로그래밍 언어와 유사한 구조로 되어 있습니다.

for loop 타입을 산술 연산 for loop 불립니다. *

* 타입의 loop 배시 2.0.4 이전에서는 사용할 없습니다.

산술 연산 for loop 형태는 Java C언어에서 보았던 같이 매우 유사합니다.

for (( initialisation ; ending condition ; update ))
do
statements...
done

loop 가지 부분이 있습니다. 처음 가지는 산술 표현식이고 마지막은 지난 번에 보았던 일반적인 loop 문과 같은 구문으로 되어 있습니다.

번째 표현식인 initialisation loop 초기값이 무엇인지 그리고 loop 참이면 계속 진행할지 평가합니다. 그렇지 않다면 loop 뛰어넘고 다음 구문을 계속해서 진행합니다.

loop 초기 값이 참일 , ending condition 평가합니다.

이것이 참이라면 구문을 수행하고 update 평가하고 ending condition 평가하여 과정을 다시 반복합니다.

loop 문은 ending condition 거짓이거나 loop 문이 하나의 구문을 통해 종료될 때까지 반복됩니다.

보통 initialisation 산술 변수로 설정되어 초기 값으로 사용됩니다. update 변수를 갱신하고, ending condition 변수를 검사하는데 사용됩니다.

어떤 값이든 자동적으로 참으로 평가되는 것을 제외합니다.

Any of the values may be left out in which case they automatically evaluate to true.

간단한 예를 들어 봅시다.

for ((;;))
do
read var
if [ "$var" = "." ]; then
break
fi
done

loop 문은 “.”으로 구성된 행을 찾을 때까지 계속해서 읽을 것입니다. 다음 과제에서 for loop 구문에 산술 연산 표현식을 사용해 것입니다.

 

 

 

 

Task 6-2

 

for loop 구문을 이용하여 숫자 1부터 12까지 곱셈을 출력하는 스크립트를 작성해보세요.

 

 

 

 


과제는 nested for loops 이용하는 좋은 케이스입니다.

for (( i=1; i <= 12 ; i++ ))
do
     for (( j=1 ; j <= 12 ; j++ ))
     do
             echo –ne "$(( j * i ))\t"
     done
     echo
done

[ 결과 ]

 

변수 i 이용하여 for loop 스크립트를 시작합니다. initialisation 절에서 i 1 설정되고, ending condition 구문은 i 12까지 한도를 비교하는 검사를 하고, update 구문은 i loop 때마다 1 더해줍니다.

loop 안에 다른 for loop 문이 있습니다. 여기서는 변수 j 이용합니다.
for loop
에서 j 라는 것을 제외하고 동일하게 동작합니다.

j loop 문에는 개의 변수를 곱하여 trailing tab 함께 출력되는 echo 구문이 있습니다.

의도적으로 새로운 행을 출력하지 않아서 (echo –n 옵션을 사용) 줄에 숫자들이 보입니다.

안쪽 loop 한번 맞췄을 새로운 행이 출력되면서 다음 행에서 시작됩니다.

산술 연산 for loop 배열에서 다룰 유용합니다. 다음 장에서 보겠습니다.

6.4 Arrays

pushd popd 함수는 문자형 변수를 이용하여 디렉터리 목록을 저장하고 문자열 패턴 매칭 연산자로 목록을 조작합니다.

방법이 문자열의 시작이나 끝에 항목을 추가하거나 조회할 유용하긴 하지만 밖에 다른 부분에 있는 항목에 접근하려고 시도할 때는 어려울 수도 있습니다. 일례로 getNdirs 함수를 이용해 N 항목을 구하고자 때입니다.

항목의 숫자, 혹은 인덱스를 명시하여 검색하는 것은 매우 좋습니다.

배열은 이것을 있도록 합니다. *

* 배시 2.0 이전 버전에서는 사용할 없습니다.

배열은 값이 있는 슬롯의 연속과 같습니다.

슬롯을 element 라고 하고, element 숫자 인덱스를 통해 접근할 있습니다.

배열 element 문자열 혹은 숫자를 포함할 있고, 다른 어느 변수처럼 사용할 있습니다.

 

배열의 인덱스는 0부터 시작해서 매우 숫자까지 계속 해서 상승합니다. †

실제로 599147937791까지 사용합니다. 거의 6백만이니 엄청나게 숫자입니다.

예를 들어 names 배열의 5번째 element names[4] 됩니다.

인덱스는 0 같거나 그보다 값으로 계산된 유효한 산술 연산 표현식이면 사용할 있습니다.

배열에 값을 할당하는 가지 방법이 있습니다.

가장 간단한 방법은 다른 변수와 마찬가지로 할당 구문을 사용하는 것입니다.

names[2]=alice
names[0]=hatter
names[1]=duchess

hatter element 0, duchess element 1, alice names 배열의 element 2 할당합니다.

다른 방법은 복합 할당을 이용하여 값을 할당합니다.

names=([2]=alice [0]=hatter [1]=duchess)   

이것은 번째 예제에서와 동일하지만 배열을 값을 설정하여 초기화 있기 때문에 편합니다.

인덱스를 순서를 명시하지 않고 사용한다는 것에 주목하십시오.

사실 다음과 같이 순서를 맞게 입력한다면 인덱스를 제공할 필요가 없습니다.

names=(hatter duchess alice)

배시는 element 0부터 시작하여 연이어 자동적으로 할당해줍니다.

복합 할당에서 특정 포인트를 인덱스로 제공하면 값이 거기에서부터 연속적으로 할당이 됩니다.

names=(hatter [5]=duchess alice)

hatter element 0, duchess element 5, alice element 6 할당합니다.

배열은 형식대로 할당 하면 자동적으로 생성해줍니다.
배열을 생성하려면 declare –a 옵션을 이용합니다.

declare 배열을 설정하면 읽기 전용 변수와 같은 속성은 전체 배열에도 적용이 됩니다.

예를 들어 declare –ar names 구문은 읽기 전용 names 배열을 생성합니다.

모든 배열의 element 읽기 전용이 됩니다.

배열을 구성하는 element ${array[i]} 문법으로 참고됩니다.

위의 마지막 예에서와 같이 ${names[5]} 하면 duchess 문자열이 출력될 것입니다. 인덱스를 입력하지 않았다면 배열은 element 0 이라고 간주할 것입니다.

또한 특수 인덱스인 @ * 사용할 수도 있습니다. 이것들을 사용하면 배열에 들어있는 모든 값들을 반환해줍니다. 사용방법은 위치 매개변수에서 사용하던 방식과 같습니다. 따옴표 안에서 배열을 사용할 경우, * 사용한다면 IFS 문자들로 이어져있는 하나의 단어로 값을 반환해주며, @ 사용한다면 각각의 분리된 단어들로써 반환해줍니다. 따옴표로 묶지 않았을 경우에는 @, * 모두 각각의 분리된 단어들로 값을 반환해줍니다. 위치 매개변수 때와 마찬가지로, 기능은 for 내에 위치했을 경우 매우 유용합니다.

for i in "${names[@]}"; do
    echo $i
done

배열은 할당된 내용이 존재 하지 않으면 의미가 없습니다. 필요할 때는 문자열이 기본값이 됩니다.

그러므로 이전에 loop 예제에서 할당된 값이 있는 element 만이 출력된 것입니다.

인덱스 1, 45, 1005에만 값이 있다면 가지 값만 출력이 됩니다.

현재 배열의 값의 인덱스가 무엇인지 알고자 한다면 ${!array[@]} 이용할 있습니다.

마지막 예제에서 1 45 1005 반환됩니다.

4장에서 보았던 길이 연산자는 # 배열에 이용할 있습니다.

배열에서 element 길이를 찾기 위해서 ${#array[i]} 이용할 있습니다.

유사하게 배열에서 많은 값들을 찾기 위해 * 이나 @ 인덱스로 사용합니다.

따라서 names=(hatter [5]=duchess alice) 라면 ${#names[5]} 7 되고, ${#names[@]} 3 됩니다.

복합 배열 구문으로 존재하는 배열에 재할당을 하면 오래된 배열 대신 새로운 값을 갱신됩니다.

새로운 element 다른 인덱스만 할당해도 모든 오래된 값은 사라집니다.

예를 들어 names ([100]=tweedledee tweedledum) 다시 할당하면 hatter, duchess, alice 값은 모두 사라집니다.

배열의 일부 요소나 전체를 삭제하려면 내장 명령인 unset 이용해야 합니다.

인덱스를 명시하면 특정 element unset 됩니다.

unset  names[100] 라고 한다면 인덱스 100 값만 사라지게 됩니다.

그러나 할당 구문과는 다르게 인덱스를 지정하지 않으면 요소 0만이 아닌 전체 배열이 사라지게 됩니다. 인덱스 대신에 * 이나 @ 사용하여 전체 배열을 삭제할 있습니다.

간단한 예제는 시스템에서 계정 이름과 ID 가지고 사용자를 매치하는 배열을 사용합니다.

사용자 ID 인자로 받아 계정의 이름과 현재 시스템의 계정 개수를 출력하는 코드입니다.

for i in $(cut -f 1,3 -d: /etc/passwd) ; do
   array[${i#*:}]=${i%:*}
done
echo "User ID $1 is ${array[$1]}."
echo "There are currently ${#array[@]} user accounts on the system."

 

/etc/passwd 파일의 필드 1 3에서 인덱스를 생성하는데 cut 사용합니다.

필드 1 계정이름이고 필드 3 사용자 ID 입니다.

스크립트는 배열 요소에 해당하는 인덱스로서 사용자 ID 사용하여 인덱스를 반복하고, 요소에 맞는 계정 이름을 할당합니다. 그런 다음 제공한 인자를 배열의 인덱스로 사용하며 인덱스 값을 출력 합니다.

'BashShell Programming' 카테고리의 다른 글

챕터 5. 흐름 제어  (0) 2011.04.19
4. Basic Shell Programming  (0) 2011.02.21
3. Customizing Your Environment  (0) 2011.01.25
2장 Command-Line Editing  (0) 2011.01.17
Learning the bash Shell 정리 - 1장  (0) 2011.01.07