본문 바로가기

Academy I/Tech Academy

Unix 12 Shell Program Debugging I

Shell Program Debugging I

Shell Program의 디버깅(Debugging)

이번 강의에서는 쉘 프로그램의 디버깅시에 사용될수 있는 몇가지 유닉스
명령어와 몇가지 쉘의 기능들에 대해서 소개합니다.

1. set 명령어

이미 기본적인 set 명령어의 사용방식은 쉘 프로그램의 포지셔널 파라메터의
설정 방식에 대한 설명시에 소개했다. 그외에도 set 명령어는 쉘에
설정되어 있는 옵션을 설정 또는 해제하기 위해 사용할 수 있다.

쉘의 옵션들은 쉘의 작업 환경을 사용자가 재정의할 수 있도록 하기위해서
제공되는 쉘의 특징적인 기능이다. 자세한 쉘의 옵션들의 예는 이후 설명된다.
쉘의 옵션들은 set 명령어에 - 또는 + 지시자와 함께 set 명령어의 옵션을
지정함에 의해 설정 또는 해제할 수 있다. 즉, 쉘 옵션을 설정하기 위해서는
제어할 대상이되는 쉘 옵션에 상응하는 set 명령어의 옵션을 - 지시자와 함께
지정하며, 쉘 옵션을 해제하기 위해서는 그에 상응하는 set 옵션을 + 지시자와
함께 지정한다. 그예는 다음과 같다.

set -x ───┐
             │ 예에서 set 명령어의 옵션 x는 쉘 옵션중 xtrace에
set +x ───┘ 상응하는 옵션이다.


또한 현재 설정되어 있는 쉘 옵션들은 쉘 변수인 $- 변수의 내용을
표시함으로써 확인 가능하다. 그 예는 다음과 같다.

$echo $-
vhs

사용자의 작업을 위해 쉘이 기동될때, 일반적으로 많이 사용되는 옵션들만이
설정된다. 따라서 그외의 옵션들을 설정하기 위해서는 set 명령어를 사용해서
사용자가 직접 설정한다.

다음은 사용자가 작업환경을 제어하기 위해서 사용할수 있는 쉘 옵션들이다.

┌───┐
│Bourne├─────┒
│Korn  │Korn      ┃
├───┼─────╂────────────────────────┐
│옵션  │옵션      ┃ 의 미                                          │
├───┼─────╂────────────────────────┤
│ --   │          ┃- 로 시작되는 다음의 인자들을 옵션으로 처리하지 │
│      │          ┃않는다.                                         │
├───┼─────╂────────────────────────┤
│ -a   │allexport ┃이후에 정의되는 또는 수정되는 모든 변수들을     │
│      │          ┃자동적으로 export 한다.                         │
├───┼─────╂────────────────────────┤
│ -e   │errexit   ┃실행한 명령어의 종료상태가 0이 아닌 종료상태를  │
│      │          ┃갖으면 쉘을 종료한다.                           │
├───┼─────╂────────────────────────┤
│ -f   │noglob    ┃화일명 생성을 금지한다.                         │
├───┼─────╂────────────────────────┤
│ -h   │trackall  ┃Function이 정의되었을때 Function내에 사용된     │
│      │          ┃명령어들의 위치(절대경로)를 기억한다.           │
├───┼─────╂────────────────────────┤
│ -k   │keyword   ┃명령어 라인에 위치하고 있는 keyword=value 형식의│
│      │          ┃인자들을 처리하고, (단; 명령어 이전에 선언되어  │
│      │          ┃있는것은 제외) 그들을 명령어의 환경에 배치한다. │
├───┼─────╂────────────────────────┤
│ -n   │noexec    ┃명령어를 실행하지 않고 명령어를 읽는다.         │
├───┼─────╂────────────────────────┤
│ -t   │          ┃하나의 명령어를 실행한 후에 쉘을 종료한다.      │
├───┼─────╂────────────────────────┤
│ -u   │nounset   ┃값이 할당되어 있지않은 변수나 포지셔널 파라메터 │
│      │          ┃가 참조되면 에러를 표시한다.                    │
├───┼─────╂────────────────────────┤
│ -v   │verbose   ┃사용자가 입력한 명령어 라인을 쉘이 읽어들인     │
│      │          ┃그대로 화면상에 프린트한다.                     │
├───┼─────╂────────────────────────┤
│ -x   │xtrace    ┃실행할 명령어 라인을 쉘이 번역한후의 명령어     │
│      │          ┃라인 상태로 + 표시자를 덧붙여 프린트한다.       │
└───┴─────┸────────────────────────┘

이상의 설명으로 쉘에서 사용자가 작업 환경을 제어하기 위해 사용가능한
옵션에대한 설명은 마치겠다.

그런데 이 옵션들중 쉘 프로그램을 디버깅하기 위해 요긴하게 사용할수 있는
옵션들이 있다. 그것은 x, v 그리고 n 옵션이다.

. -x 옵션을 설정하면 화일명, 변수 그리고 명령어 대치와 입출력 방향전환이
수행된후의 상태로, 실행될 명령어 라인이 쉘에 의해 프린트 되어진다.
이 결과로 출력되는 모든 명령어 라인의 처음에는 + 심벌이 표시된다.
변수 할당 명령문은 위의 형식에서 제외된다.

. -v 옵션을 설정하면 번역전의 처리되는 각각의 명령어 라인을 표시한다.

. 이러한 옵션들은 쉘 프로그램의 디버깅시에 유용한 도구이고, 쉘 프로그램의
처음에 'set -xv' 라는 명령어를 기술함으로써 손쉽게 설정할 수 있다.

. -n 옵션은 중첩된 제어문들 즉, do ... done 또는 if ... fi 제어문의
지정 오류를 검사하는데 유용하다.

2. set 명령어 예제

여기서 현재 쉘의 v와 x 옵션을 설정및 제거하기 위해 명령어 라인상에서
set 명령어를 이용한다. 그러나 set 명령어는 쉘 프로그램에 적용하기 위해서
쉘 프로그램내에서도 지정할 수 있다.

┌──────────────────────────────────┐
│ prog                                                               │
├──────────────────────────────────┤
│ if [ $# -eq 0 ]                                                    │
│ then # 이 프로그램은 쉘 옵션을                                     │
│ set -- W X Y # 설명하기 위해 사용한다.                             │
│ fi                                                                 │
│ var=Testing                                                        │
│ echo $var $1 $2 $3 > file2                                         │
└──────────────────────────────────┘

┌─────────────────┐
│ Bourne/Korn                      │
├─────────────────┤
│$echo "$-"                        │< 현재 설정되어 있는 쉘 옵션을
│s                                 │ 확인하고 있다.
│$set -vx                          │< v 와 x 옵션을 설정하고 있다.
│$echo "$-"                        │
│echo "$-"                         │< v 옵션 설정의 결과
│+echo xvs                         │< x 옵션 설정의 결과
│xvs                               │< 명령어 라인 실행 결과
│$prog A B C                       │
│prog A B C                        │
│+prog A B C                       │
│$cat file2                        │
│cat file2                         │
│+cat file2                        │
│Testing A B C                     │
│$set +vx                          │< v 와 x 옵션을 제거하고 있다.
│set +vx                           │
│+set +vx                          │
│$echo "$-"                        │< v와 x 옵션을 제거함으로써 명령어
│s                                 │ 실행 결과만이 출력되고 있다.
│$sh -x prog D E F                 │< sh 명령어를 통해 쉘 프로그램을
│+[3 -eq 0]                        │ 실행하면서, 쉘 옵션을 함께 지정
│var=Testing                       │ 하고 있다. 여기서 변수 할당
│+echo Testing D E F               │ 제어문은 검증되지 않고 있음을
└─────────────────┘ 주목한다.

┌─────────────────┐
│ Korn                             │
├─────────────────┤
│KORN> PS4="trace> "               │< trace 모드시에 사용할 프롬프트를
│KORN> export PS4                  │ 선언하고 있다.
│KORN> set -o xtrace               │< trace 모드를 선언하고 있다.
│KORN> set -v                      │< v 옵션을 설정하고 있다.
│trace> set -v                     │
│KORN> echo $-                     │
│echo $-                           │
│trace> echo ivsxm                 │
│ivsxm                             │< 명령어의 실행 결과
│KORN> set +o verbose              │
│set +o verbose                    │
│trace> set +o verbose             │
│KORN> set +x                      │
│KORN> ksh -x progb D E F          │< progb 화일의 내용을 실행하는데
│trace> [3 -eq 0]                  │ 있어서 x 옵션을 설정하여 실행하고
│trace> var=Testing                │ 있다. Korn Shell에서는 변수할당
│trace> echo Testing D E F         │ 명령문도 검증해주고 있다.
│trace> 1> file2                   │
└─────────────────┘


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


Shell Program Debugging II

Shell Program의 디버깅(Debugging) II

3. unset 명령어

쉘 프로그램을 작성하다 보면, 이전에 정의된 변수를 제거할 필요가
있을수 있다. 이러한 변수에 대한 정의를 제거하는 작업은 unset 명령어를
사용하여 수행할 수 있다.

( 주의 ) unset 명령어는 읽기전용 변수및 IFS, PATH, PS1, PS2 등과 같은
쉘 예약 변수에는 사용될 수 없다.

이미 정의되어 있는 변수들을 확인하기 위한 env와 set 명령어를 실행해도
unset 명령어가 수행된 변수들은 표시되지 않는다.

다음은 unset 명령어의 사용예를 보여주는 예이다.

( 예제 )
$x=100
$echo $x
100
$unset x
$echo $x
<------ unset 되었음으로 아무것도 표시되지
$ 않고 있다.

4. readonly 명령어

readonly 명령어는 값이 변경되어져서는 안될 변수를 지정하기 위해서
사용된다. readonly 명령어를 통해 읽기전용 변수로 선언된 변수들은
unset 또는 값을 변경할 수 없다.

이런 읽기전용 변수 기능은 제한쉘(Restricted Shell)에서 빈번하게
사용되어진다. 이에 대한 자세한 설명은 이번 과정에서는 소개되지 않는다.

다음은 readonly 명령어를 사용하는 예이다.

( 예제 )
$readonly PATH HOME
$HOME=.
HOME: is read only

5. eval 명령어

이미 설명했던대로 명령어 라인을 커널이 실행하기 전에, 쉘에 의해
화일명 대치, 변수 대치, 입출력 방향전환등의 작업을 수행하기 위한
명령어 라인에 대한 한번의 번역 작업이 수행된다.

쉘은 하나의 명령어 라인을 해독하기 위한 일정한 번역절차를 갖고 있다.
이로인해 부정확하게 번역되어지는 변수들이 있을 수 있다. 이런 경우
eval 명령어를 통해 그 실태를 검증할 수 있다.

즉, 이와같은 경우 하나의 명령어 라인에 대하여 두번 연속하여 번역 작업을
수행하도록 지정함으로써 번역 절차로 인한 프로그램 오류를 검증할 수 있다.

다음은 명령어 라인 번역절차로 인한 명령어 라인 오류를 보여주는 예제이다.

( 예제 )

$pipe="|"
$ls $pipe wc -l
|: No such file or directory
wc: No such file or directory
-l: No such file or directory
$eval ls $pipe wc -l
16

첫번째 ls 명령어를 분석해보자.여기서 사용자는 파이프 심볼인 |를
사전에 변수 pipe에 저장해 두었다가 이를 명령어 라인상에서 참조하려고
하고 있다. 실행 결과를 보면 에러 처리가 되고 있다. 왜그럴까?
그냥 보면은 전혀 명령어 라인상에 오류가 없는 것으로 보인다.
그러나 자세히 보면은 중대한 오류가 있음을 볼수 있다.
파이프를 통한 출력 방향전환은 쉘에의해 처리가 되어야 하는데, 쉘은
pipe라는 변수를 파이프 심볼인 |로 대치한후 이를 그대로 커널에
전달하여 실행을 요구했기 때문에 이와 같은 결과가 초래된 것이다.
( 이 부분에 대해 잘 이해가 가지 않을 경우에는 이미 강의한 쉘의
명령어 라인 처리과정 강의를 다시 확인하기 바란다. )

이경우 두번째 예제와 같이 eval 명령어를 실행함으로써 프로그램을
검증할 수 있다. 즉 eval 명령어 수행후 정상적인 작업이 수행되는
것으로 번역절차로 인한 오류임을 확인할 수 있다.


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


Shell Program Debugging III

Shell Program의 디버깅(Debugging) III

6. cat 명령어를 사용한 프로그램 검증

프로그램을 수행하다보면 쉘 프로그램 또는 데이타 화일속에 화면상에는
표시되지 않는 제어문자(Control Character)로 인해 에러가 발생하는 경우가
있을수 있다. 제어문자들은 화면상에 표시만되지 않을뿐 엄연한 하나의
문자로 처리되기 때문에 명령어 라인의 분석시 엉뚱한 결과를 가져올수 있다.
뿐만아니라 제어문자들로 인하여 터미널이 다운되는 현상 즉, Hang up을
초래할 수도 있기 때문에 프로그램내의 제어문자 존재 여부를 확인할 필요가
있다.

위와같이 프로그램 또는 데이타 화일속에 혹시 들어 있을수도 있는
제어문자들의 확인은 cat 명령어와 함께 그의 옵션을 사용하여 수행할 수
있다. 다음은 cat 명령어의 옵션에 대한 설명이다.

┌───┬──────────────────────────────┐
│ 옵션 │ 의 미                                                      │
├───┼──────────────────────────────┤
│ -e   │각 라인의 끝에 $ 심볼을 붙인다. v 옵션과 함께 사용한다.     │
├───┼──────────────────────────────┤
│ -t   │탭 문자를 대신하여 ^i로 표시한다. v 옵션과 함께 사용한다.   │
├───┼──────────────────────────────┤
│ -v   │프린트 불가능한 제어문자를 대치 형식으로 표시한다.          │
└───┴──────────────────────────────┘

다음은 v 옵션 지정시 제어문자를 대신하는 대치 형식을 나타낸다.

┌───────┬────────┐┌───────┬────────┐
│ASCII 코드값  │ 대치 형식      ││ASCII 코드값  │      대치 형식 │
┝━━━━━━━┿━━━━━━━━┥┝━━━━━━━┿━━━━━━━━┥
│ '\000'       │ ^@             ││ '\021'       │ ^Q             │
├───────┼────────┤├───────┼────────┤
│ '\001'       │ ^A             ││ '\022'       │ ^R             │
├───────┼────────┤├───────┼────────┤
│ '\002'       │ ^B             ││ '\023'       │ ^S             │
├───────┼────────┤├───────┼────────┤
│ '\003'       │ ^C             ││ '\024'       │ ^T             │
├───────┼────────┤├───────┼────────┤
│ '\004'       │ ^D             ││ '\025'       │ ^U             │
├───────┼────────┤├───────┼────────┤
│ '\005'       │ ^E             ││ '\026'       │ ^V             │
├───────┼────────┤├───────┼────────┤
│ '\006'       │ ^F             ││ '\027'       │ ^W             │
├───────┼────────┤├───────┼────────┤
│ '\007'       │ ^G             ││ '\030'       │ ^X             │
├───────┼────────┤├───────┼────────┤
│ '\010'       │ ^H             ││ '\031'       │ ^Y             │
├───────┼────────┤├───────┼────────┤
│ '\011'       │ ^I             ││ '\032'       │ ^Z             │
├───────┼────────┤├───────┼────────┤
│ '\012'       │ $              ││ '\033'       │ ^[             │
├───────┼────────┤├───────┼────────┤
│ '\013'       │ ^K             ││ '\034'       │ ^\             │
├───────┼────────┤├───────┼────────┤
│ '\014'       │ ^L             ││ '\035'       │ ^]             │
├───────┼────────┤├───────┼────────┤
│ '\015'       │ ^M             ││ '\036'       │ ^^             │
├───────┼────────┤├───────┼────────┤
│ '\016'       │ ^N             ││ '\037'       │ ^_             │
├───────┼────────┤├───────┼────────┤
│ '\017'       │ ^O             ││ '\040'       │                │
├───────┼────────┤├───────┼────────┤
│ '\020'       │ ^P             ││ '\177'       │ ^?             │
└───────┴────────┘└───────┴────────┘

다음은 화일속의 제어문자를 확인해보는 cat 명령어의 예이다.

┌──────────────────┐
│file1                               │
├──────────────────┤
│This data file has spaces           │ 이 화일을 자세히 보면
│ and tabs and newline, but I        │ 화일내 각각의 단어들간에
│cannot see where they are. It also  │ 간격이 일정하지 않는 것을
│has a bunch of unprintable          │ 볼수 있다. 왜 그럴까?
│characters                          │ 아마 사용자가 일부러 단어들
│                                    │ 사이에 공백을 지정하여
│                                    │ 간격을 두지는 않았을 것이다.
│embedded in it.                     │ 이 경우 cat 명령어를 통해
└──────────────────┘확인할 수 있다.

$cat -vet file1
This data file has spaces$ 이 결과상에서 각각의 라인의
and^Itabs and newline, but I$ 끝에는 e 옵션으로 인해 $가
cannot see where they are. It also$ 표시되고 있고, t 옵션으로
has a bunch of unprintable$ 인해 탭 문자 대신에 ^I가
characters$ 표시되고 있고, 기타 제어문자를
^A^B^C^E^F^I$ 대신해 대치문자가 표시되고
^K^L$ 있는것을 확인할 수 있다.
^V^W^X^Y^Zembedded in it.$

## 제어문자 각각의 정확한 의미는 ASCII 코드표를 참조하기 바란다. ##


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


Parameter Modification

Shell Program의 제어문: Parameter의 응용

이번 강의부터는 쉘 프로그램을작성함에 있어서 보다 효과적인
프로그램을 작성할 수 있도록 지원하는 Bourne / Korn Shell의
다양한 제어문에 대하여 설명드립니다.

1. 파라메터의 응용 개요

쉘은 기본적으로 $parameter 형식의 파라메터 참조시에는, 사전에
parameter에 할당되어 있는 값으로 그를 대치해준다.

그러면 파라메터에 값이 설정되어 있지 않은 경우는 어떻게 처리될까?
이것은 이전 강의에서 파라메터가 미설정되어 있을 경우에는 null 처리됨을
확인했다.

그렇지만 파라메터를 운용함에 있어서, 파라메터가 설정 또는 null인가에 따른
파라메터의 특정 문자열로의 조건적 대치를 특수기호들을 사용함에 의해
행할수 있다.( 이들 특수 기호들은 이후 설명된다. )

쉘에 의한 파라메터의 기본적인 대치 자업 내용을 요약하면 다음과 같다.

파라메터가 설정되어 있는 경우 : 설정 값으로 대치
파라메터가 미설정되어 있는 경우 : null 값이 대치
파라메터가 null로 설정되어 있는 경우 : 대치가 수행되지 않는다.

다음은 파라메터의 대치 작업을 보여주는 예이다.

예제)

$fruit=apples
$echo fruit $fruit <-- 파라메터를 참조할때 $parameter 형식으로
fruit apples 지정해야 함을 보여주고 있다.
$fruit= <-- 파라메터에 설정된 값을 제거하고 있다.
$veg="" <─┐ 두가지 파라메터에 null 값을 설정하고
$meat='' <─┘ 있다.
$echo $fruit $veg $meat $undef

$

2. 파라메터 값의 조건적 수정 1

파라메터 값의 조건적 수정은 다음과 같은 방식으로 수행할 수 있다.

${parameter:-value}
if 파라메터가 설정되어 있고, null이 아니면
then
파라메터에 설정된 값을 사용
else
지정한 value로 파라메터를 대치한다.

${parameter:=value}
if 파라메터가 설정되어 있고, null이 아니면
then
파라메터에 설정된 값을 사용
else
지정한 value로 파라메터를 대치하고,
파라메터에 value를 설정한다.

위 두가지 방식의 차이점을 주목해야 한다.
:- 형식에서 지정한 value는 일시적으로 사용할뿐, 파라메터는 미설정되어
있는 상태를 그대로 유지한다.
:= 형식에서는 지정한 value를 참조할 뿐만아니라, 그 value를 파라메터에
설정해준다는 차이가 있다.

( 주목 )

Korn Shell에서는 위 지시문 형식에서 :을 생략 가능하다.

다음은 파라메터 값의 조건적 수정을 보여주는 예제들이다.

Bourne/Korn
┌──────────────────┐
│ $echo $VALUE                       │<파라메터가 미설정되어 있음을
│                                    │ 보여주고 있다.
│ $echo "Value is ${VALUE:-NEW}"     │<파라메터가 미설정되어 있음으로
│ Value is NEW                       │ 지정한 NEW가 대치되고 있다.
│ $echo $VALUE                       │<명령 실행후에도 파라메터가
│                                    │ 미설정상태 그대로이다.
│ $VALUE='junk'                      │<이후의 예는 파라메터에 값을
│ $echo "Value is ${VALUE:-NEW}"     │ 설정한후 처리 형태를 보여주고
│ Value is junk                      │ 있다.
│                                    │
└──────────────────┘

Korn
┌───────────────────┐
│ KORN> print $TEST                    │<다음 예는 Korn Shell에서
│                                      │ 파라메터 값의 수정 예를 보여
│ KORN> echo ${TEST=/usr/bin/page}     │ 주고 있다. 지정 형식에서
│ /usr/bin/page                        │ :이 사용되지 않았음을 주목
│ KORN> print $TEST                    │ 하기 바란다.
│ /usr/bin/page                        │
│ KORN> TEST='new value'               │
│ KORN> print "${TEST:=/usr/bin/page}" │
│ newvalue                             │
│ KORN> print $TEST                    │
│ new value                            │
└───────────────────┘

3. 파라메터 값의 조건적 수정 2

파라메터 값의 조건적 수정을 수행하기 위한 또다른 형식은
다음과 같다.

${parameter:?value}

if 파라메터가 설정되어 있고, null이 아니면
then
설정되어 있는 값을 사용
else
value를 출력하고, 명령어를 종료한다.

이 형식에서 만일 value를 생략하면, 쉘은 다음 메세지를 표시한다.
"parameter null or not set"

이 형식은 특정 작업에 필요로하는 변수가 설정되었는지를
검사하는데 있어서 매우 유용하다.

${parameter:+value}

if 파라메터가 설정되어 있고, null이 아니면
then
지정한 value로 파라메터를 대치
else
아무런 대치를 수행하지 않는다.

( 주목 ) Korn Shell에서는 : 을 생략 가능하다.

다음은 위 형식들과 관련된 예제이다.

Bourne/Korn
┌──────────────────┐
│ $echo $X                           │<파라메터가 미설정되어 있음을
│                                    │ 보여주고 있다.
│ $echo ${X:?}                       │<파라메터가 미설정되어 있어서
│ X:Parameter null or not set        │ 디폴트 에러 메세지가 표시된다.
│ $echo ${X:?Substitute}             │<디폴트 에러 메세지를 사용자가
│ X:Substitute                       │ 재정의한 것을 보여주고 있다.
│ $echo $X                           │<명령 실행후에도 파라메터가
│                                    │ 미설정 상태로 그대로 있다.
│ $X=4                               │<파라메터가 설정되어 있을
│ $echo ${X:?Substitute}             │ 경우에는 그 값이 그대로
│ 4                                  │ 사용됨을 보여주고 있다.
└──────────────────┘

Korn
┌───────────────────┐
│ KORN> echo $X                        │< 파라메터가 미설정되어 있다.
│                                      │
│ KORN> echo ${X:+NEW_VALUE}           │< 파라메터가 미설정되어 있어
│                                      │ 아무 처리도 행하지 않는다.
│ KORN> X=SET                          │< 파라메터를 설정했다.
│ KORN> echo ${X+NEW_VALUE}            │< 이미 파라메터가 설정되어
│ NEW_VALUE                            │ 있기 때문에, 지정한 값을
│ KORN> echo $X                        │ 파라메터 값으로 사용하고
│ SET                                  │ 있다.
└───────────────────┘


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




Shell Program의 제어문 I

쉘 프로그램의 제어문 I

1. :(Colon) Null 명령어

Null 명령어는 실제로 아무런 처리도 행하지않는(?) 쉘 고유의 명령어이다.

특정 제어문을 사용하는 경우, 해당 제어문을 사용하기 위해서는 제어문에
불필요하다 하더라도 문법상 반드시 명령어를 지정해야만 하는 경우가 있다.

예를들면 if-then-else-fi 제어문을 사용할때, 사용자는 제어문상의 각각의
구문 모두를 반드시 지정해야만 한다. 그런데 이 제어문을 사용할때
if 구문의 조건 판단의 결과, 거짓인 경우의 제어만이 필요로 하다고
가정하자. 이 경우에 then 구문을 생략하는 것은 문법상 허용되지 않는다.
그렇다고 then 구문을 지정하고 필요로 하지도 않는 다른 명령어를
then 구문에 지정할수도 없을 것이다.바로 이런 경우에 Null 명령어를
사용하면 된다.

그렇지만 : 명령어를 사용할때 주의할 것이 있다. : 명령어는 유닉스의
이전 버젼에서 원래 주석 기호로 사용되었으나, 현재는 # 심볼이 주로 주석
기호로 사용되고 있다. 그러나 아직도 : 기호도 주석 심볼로 사용하고 있다.
# 기호를 사용하여 주석을 지정했을 경우에는, 그 이후에 기술되어 있는
명령어 라인을 쉘이 분석을 수행하지 않지만, 이와달리 : 심볼로 주석을
지정했을 경우에는 :이 지정되어 있는 명령어 라인을 분석한다.
그렇다고 : 이후에 지정된 명령어들을 실행한다거나 에러를 생성하지는
않는다. 이미 강의했던 쉘의 분석과정에서 수행되는 작업이 모두 수행되나,
명령어의 실행은 되지 않는다는 것이다. (자세한 내용은 이전에 강의했던
쉘의 명령어 라인 분석 과정을 참조하기 바란다.)
이로인해 :가 지정된 이후의 명령어 라인에 특정 화일로의
출력 방향전환(Redirection)이 지정되어 있을 경우, 출력 화일은 생성되나
내용을 갖지 않는 빈 화일로 생성되게 된다.


:(colon) 명령어가 null 명령어로써 정확하게 수행되게 하려면, : 명령어를
반드시 명령어 라인의 첫번째 명령어로 지정해야만 한다. ( 쉘은 명령어 이름
앞에 지정된 공백들은 무시한다. )

다음은 : 명령어의 사용예를 보여준다.

┌─────────────┐
│$ls                       │
│$if test -f f1            │ null 명령어를 사용했을 경우의 처리결과를
│>then                     │ 보여주는 예이다.
│> : <-----------------이 경우에는 if문의 판단 결과가 참인 경우
│>else                     │ 아무런 작업도 수행되지 않는다.
│> echo f1 does not exist  │
│> : > f2 <-----------------null 명령어 이후에 출력방향 전환 심볼이
│>f1                       │ 지정되어 있다. 쉘이 명령어 라인을
│f1 does not exist         │ 분석하는 과정에서 화일은 생성하나
│$ls                       │ 빈 화일로 생성한다.
│f2 <-----------------화일이 생성된것을 확인할수 있다.
│$if [ ! -f f1 ]           │
│>then                     │
│> : echo hello | tee f1 <-----이번에는 : 명령어의 뒤에 명령어와
│>fi                       │ 방향전환 심볼을 지정해보았다.
│$ls                       │ 이후의 과정을 통해 화일은 생성되나
│f1 f2                     │ echo 명령어는 실행되지 않음을
│$cat f1                   │ 확인할 수 있다. 그로인해 f1 화일에는
│$                         │ 아무 내용도 들어있지 않게된다.
└─────────────┘

2. .(Dot) 명령어

. 명령어는 후속되는 쉘 프로그램 또는 스크립트 화일을 현재의 쉘에서
실행하도록 쉘에게 지시하는 명령어이다. 이때 쉘 프로그램내의 각각의
명령어들 또는 스크립트상에 존재하는 각각의 명령어들은 마치
프롬프트에서 개개로 입력하여 실행한 것처럼 현재 쉘에서 실행되게 된다.

따라서 . 명령어를 이용하여 실행한 쉘 프로그램 또는 스크립트내에
쉘 작업환경을 제어하는 쉘 변수에 대한 재정의가 지정되어 있을 경우에
실행후 현재 쉘의 작업 환경이 재정의되게 된다.
이와같이 . 명령어는 쉘 프로그램내에서 현재의 쉘 환경을 재정의하기
위해 유용하게 사용할 수 있다.

다음은 . 명령어를 이용한 예이다.

┌────────────────────────────────┐
│ /mjr/p # 이 화일에는 PATH 변수를 재정의하는 명령이 들어있다.   │
├────────────────────────────────┤
│ PATH=/bin:/usr/bin:/etc:/mjr                                   │
└────────────────────────────────┘

$echo $PATH # 현재 PATH 변수에 설정된 값을 확인해 보고 있다.
/bin:/usr/bin:/etc # 여기서 현재의 디렉토리를 의미하는 .이 지정되어
# 있지 않음을 주목해야 한다. 이런 경우에는 현재의
# 디렉토리내에 있는 화일을 실행하기 위해서는
# 반드시 절대 경로명을 지정해야 한다.
$ls mjr # mjr 디렉토리내에 p,prog1,prog2라는
p # 3개의 화일이 있음을 확인해주고 있다.
prog1
prog2
$cd mjr # 작업 디렉토리를 변경
$/mjr/p # 절대경로명을 지정하여 p 화일을 실행하고 있다.
# PATH에 .이 지정되어 있지 않기 때문에
$prog1 # 직전에 실행시킨 p 화일에 선언되어 있는 PATH
prog1:not found # 변수에서는 mjr 디렉토리가 들어있기 때문에
# prog1이라고만 지정해도 실행되어야 하는데
# 에러가 발생하고 있다. 왜 그럴까? 그 이유는
# p 화일은 현재의 쉘에서 실행되지 않고 서브 쉘에서
# 실행되었기 때문이다.

위의 예를 그림으로 나타내면 다음과 같다.

                   ┌────────────────┐
 현재의 쉘         │ login PATH=/bin:/usr/bin:/etc  │
                   └────┬─────────┬─┘
                             │                  │
       ┌──────────┴───────┬─┴──┐
서브쉘 │/mjr/p PATH=/bin:/usr/bin:/etc:/mjr │ prog1  │
       └──────────────────┴────┘

$. /mjr/p # 이번에는 . 명령어를 통해 p 화일을 실행해 보았다.
$prog1 # 이 화일내에 선언되어 있는 PATH 변수의 재정의가 현재의
$ # 쉘에 그대로 반영되었기 때문에 prog1이 실행된다.

위의 예를 그림으로 나타내면 다음과 같다.

┌──────────────────┐
│login PATH=/bin:/usr/bin:/etc       │
│/mjr/p PATH=/bin:/usr/bin:/etc:/mjr │
└───────┬──────────┘
                │
            ┌─┴──┐
            │ prog1  │
            └────┘

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


Shell Program의 제어문 II

쉘 프로그램의 제어문 II

1. (...)와 {...} 명령어

둥근괄호와 중괄호 제어문은 실행을 위해 명령어들을 그룹화하기 위해
사용한다. 그러나 이들 제어문은 명령어를 실행하는 방식에 있어서
서로 상이하게 작업을 수행한다.

둥근괄호 제어문은 한쌍의 둥근괄호 속에 지정되어 있는 명령어들을
서브쉘에서 실행한다.

중괄호 제어문은 한쌍의 중괄호 속에 지정되어 있는 명령어들을
현재의 쉘에서 실행한다.

( 주의 ) 중괄호를 사용할 경우에는 좌측의 중괄호 다음에는
반드시 공백을 지정해야 하고, 지정하는 마지막 명령어
다음에는 반드시 세미콜론을 지정해야 한다.

예> { cd /etc; ls;}

다음은 둥근괄호, 중괄호 제어문을 사용하는 예이다.

$pwd
/usr/acct/user1
$(cd /bin; ls)
accterm csh i286 ls rm sort u3b
adb date i286emul mail rmail spc u3b15
...
$pwd
/usr/acct/user1

위의 예제 프로그램이 실행되는 과정을 그림으로 그려보면 다음과 같다.
Built-in 명령어인 pwd 명령어는 현재의 쉘에서 실행되지만, 둥근괄호로
둘러쌓인 Built-in 명령어들은 서브쉘에서 실행되어진다.
그로인해 비록 디렉토리의 이동 명령을 실행했지만, 실행후 디렉토리의
이동은 실제 수행되지 않은것을 볼수 있다.

          ┌──┬────────┬──┐
현재의 쉘 │pwd │ login shell    │pwd │
          └──┴─────┬──┴──┘
                            │
                        ┌─┴──┐
서브 쉘                 │cd /bin │
                        │ls      │
                        └────┘

$pwd
/usr/acct/user1
${ cd /bin; ls;}
accterm csh i286 ls rm sort u3b
adb date i286emul mail rmail spc u3b15
...
$pwd
/bin

위의 예제를 그림으로 그려보면 다음과 같다.
위에서 둥근괄호속에서 수행된 디렉토리의 이동은 실제로 수행되지는
않았지만, 중괄호속에서 지정된 디렉토리 이동은 현재의 디렉토리속에서
수행되기 때문에 실제로 이동한 결과를 보여주고 있다.

                         ┌────────┐
현재의 쉘      ┌──┐  │ login shell    │
               │pwd ├─┼──            │
               └──┘  │ cd /bin        │
                         │ ls             │  ┌──┐
                         │          ───┼─┤pwd │
                         └────────┘  └──┘


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


Shell Program의 제어문: case

쉘 프로그램의 제어문 : case

1. case 제어문

case 문장은 특정값의 유형에 따라 별도의 작업처리를 지정할수 있도록
지원하는 제어문이다.

if 제어문은 두가지 조건(참과 거짓)에 대해서 검사를 수행한후,
그 결과에 상응하는 작업을 실행하는 반면에, case 제어문은 제어문상에
나열되어 있는 복수개의 유형과 특정값을 비교하여 그에 일치하는 유형에
지정된 작업을 수행한다.

case 제어문상에서 특정 유형별 별도의 작업 내용을 분기(branch)라 하는데,
분기 지정시에 주의할 것이 있다. 그것은 각각의 분기들은
세미콜론 두개(;;)를 사용하여로 구별해야 한다는 것이다.
( 단, 마지막 분기는 임의로 지정가능하다. ).

또한 case 구문을 사용할 경우 반드시 구문의 마지막에 esac를 지정하여
구문을 종료해야 한다는 것이다.

다음은 case 제어문의 기본 골격이다.

case value in <---- value의 값에 따라서 아래의 유형별로
pattern1) 별도로 지정된 명령어들이 실행된다.
command1
command2
...
;; <---- 하나의 유형 지정이 끝날때마다
pattern2) ;;을 지정해야만 한다.
command3
command4
...
;;
...
patternN)
command5
command6
...
;; <----- 마지막 유형에서의 ;; 는 생략 가능.
esac <------------------ case 문의 종료를 표시.

다음은 case 제어문의 실행과정을 보여주는 흐름도이다.

               case문의 실행 개시
                        │
                  ┌──┴──┐
                  │value=    │ <-- value의 값이 무엇이냐에 따라
                  └──┬──┘ 일치하는 유형이 수행.
                        │
               pattern1 │pattern2 pattern3
      ┌────────┼────────┐
      │                │                │
┌──┴───┐  ┌──┴───┐  ┌──┴───┐
│ command1   │  │ command3   │  │ command5   │
│ command2   │  │ command4   │  │ command6   │
└──┬───┘  └──┬───┘  └──┬───┘
      │                │                │
      └────────┼────────┘일치된 패턴이 실행된후
                        │ case문이 종료
                 case문의 실행 종료

case 문장내의 분기할 유형의 판단의 근거로 사용하는 value는 명시적인 값을
지정해야만 한다.( 정규 표현식(Regular __EXPRESSION__)이나 화일명 확장문자를
지정하면 않된다.)
또한 case 제어문상에 | 심볼을 이용하여 논리적 OR 조건을 지정할 수 있다.

case 제어문에서 유형은 이전에 강의한 정규 표현식과는 다르다.
유형 지정시에 메타문자인 *, ? 그리고 [...]은 사용할 수 있다.
그러나 이들 문자들은 화일명 확장이나 정규 표현식의 메타문자들과
동일하게 번역되지 않고, 다음과 같이 다른 의미로 해석된다.

┌─────┬──────────────────────────┐
│메타문자  │ case 유형내에서의 의미                             │
├─────┼──────────────────────────┤
│ *        │0 또는 그이상의 반복문자들과 대치                   │
├─────┼──────────────────────────┤
│ ?        │임의의 한문자와 대치                                │
├─────┼──────────────────────────┤
│ []       │사각괄호내에 지정되어 있는 문자중 하나의 문자       │
└─────┴──────────────────────────┘


( 주목 ) 다시한번 말하면, case 제어문에서 이들 메타문자들은
정규 표현식및 화일명 확장기호로 사용되지 않는다.
이들 문자들은 case 제어문상에서의 독특한 의미를 갖는다.

case 구문에서 모든 값에 대해 수행할 분기를 지정하기 위해 유형을 선언할때
* 메타문자를 사용한다. 이 문자를 제어문상에서 정확하게 사용되도록 하기
위해서는 case의 제어문의 마지막 분기에 지정해야 한다. 마지막 분기가
아닌 다른 분기에 지정했을 경우 그 이후의 분기에 지정된 유형의 비교는
지정의 의미가 없기 때문이다.

다음은 case 문장을 사용을 이용하여 작성한 프로그램이다.

┌─────────────────────────────┐
│progb                                                     │
├─────────────────────────────┤
│if [ $# -ne 1 ]                                           │
│then                                                      │
│ exit                                                     │
│fi                                                        │
│echo 'Enter a word: \c'                                   │
│read x # 프로그램 실행시 사용자가 입력한                  │
│case $x in # 값이 무엇이냐에 따라서 별도의                │
│ he??o) echo $x --- bye # 작업이 처리된다.                │
│ ;;                                                       │
│ h[ai] echo $x --- by                                     │
│ ;;                                                       │
│ b*ye | by) echo $x --- hello                             │
│ ;;                                                       │
│ *) echo $x try again later                               │
│esac                                                      │
└─────────────────────────────┘

$progb
Enter a word:heXzo <--- 입력한 값이 case 문에 지정된 유형중
heXzo --- bye he??o에 해당된다. 여기서 ?는 한문자의
$progb 임의의 문자를 의미한다.
Enter a word:ha <--- 입력값이 h[ai]에 해당한다.
ha --- by h[ai]는 ha 또는 hi에 상응한다.
$progb
Enter a word:baaaaaaye <--- 입력값이 b*ye | by에 해당한다.
baaaaaaye --- hello 여기서 b*ye는 첫문자가 b이고 끝의 두문자가
ye인 문자열에 해당한다. 단 이들사이에
문자가 있을 경우에는 동일한 문자
이어야 한다. 그리고 |는 논리적 OR 조건을
의미한다.
$progb
Enter a word:by
by --- hello
$progb
Enter a word:abcdefg <--- 입력값이 처음 세가지의 분기의 유형에
abcdefg try again later 해당되지 않는다. 그래서 마지막 유형으로
지정된 * 즉 임의의 유형에 해당되어
그에 상응하는 명령어가 실행되고 있다.


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


Shell Program의 제어문: || 와 &&

쉘 프로그램의 제어문 : || 와 &&

1. 테스트 제어문

쉘에는 ||와 &&라는 if 문장을 좀더 간략하게 사용할 수 있도록 하기위한
단축형 제어문이 있다. 이들 명령어들은 앞서 지정된 명령어의 실행종료
상태에 따라 별도의 후속 명령어를 실행하도록 작업을 분기하도록 처리할
수 있는 간편한 기능을 제공한다.

&& 제어문은 if-then-fi 제어문의 단축형 제어문이다.
이 제어문의 형식은 다음과 같다.

┌────────────┐
│ command1 && command2   │
└────────────┘


이 제어문은 command1의 실행 종료상태가 0일 경우 즉, 참일 경우에
command2가 실행되나, command1의 실행 종료상태가 0이 아닐경우
즉, 거짓일 경우에는 command2가 실행하지 않고 제어문은 종료된다.

기본적인 if-then-fi 제어문의 형식은 다음과 같다.

┌───────────┐
│ if command1          │
│ then                 │
│ command2             │
│ fi                   │
└───────────┘

|| 제어문의 형식은 다음과 같다.

┌────────────┐
│ command1 || command2   │
└────────────┘

이 제어문은 command1의 실행 종료상태가 0이 아닐경우 즉, 거짓일 경우에
command2를 수행하고, 실행 종료상태가 0인 경우에는 command2를 실행하지
않고 제어문을 종료한다.

|| 제어문에 상응하는 if-then-else-fi 제어문의 형식은 다음과 같다.

┌──────────┐
│ if command1        │
│ then               │
│ :                  │
│ else               │
│ command2           │
│ fi                 │
└──────────┘

다음은 이들 제어문의 사용예이다.

$who <-------------(1)
mjr term/22 Oct 31 14:20
$who | grep user1 > /dev/null && echo "user logged on" <---(2)
$if who | grep user1 > /dev/null <-------------(3)
>then
> echo "user logged on "
>fi

# 위의 예에서 (1)의 실행 결과를 보면 현재 시스템에 user1이라는
# 사용자는 로그인하지 않았음을 확인할 수 있다. 그후 (2)에서 && 제어문을
# 사용한 명령어 라인을 지정하고, 실행하였다. 명령어 라인을 실행한후
# 출력이 없는것으로 보아 echo 명령어는 실행되지 않았음을 알수 있다.
# 왜 그럴까?
# 그것은 && 제어문의 앞에 지정되어 있는 grep 명령어가 에러 처리됐기
# 때문이다. (3) 부분에서는 동일한 작업을 수행하도록 if 문을 사용하는
# 예를 보여주는 예제이다.

$who | grep user1 > /dev/null || echo "user not logged on" <-----
user not logged on
$if who | grep user1 > /dev/null
>then
> :
>else
> echo "user not logged on"
>fi
user not logged on

# 이번에는 동일한 작업을 &&가 아닌 || 제어문을 사용해서 지정해 보았다.
# 그런데 이번에는 echo 명령어가 실행되어 결과가 출력되고 있는것을 볼수
# 있다. 그것은 || 제어문은 앞의 명령의 실행 결과가 거짓일 경우에
# 후속되는 명령어를 실행하기 때문이다.


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


Shell Program의 제어문: for

쉘 프로그램의 제어문 : for loop

1. for-in-do-done 반복 제어문

for 명령어는 지정된 각각의 요소들에 대해 특정 명령어 또는 일련의
명령어를 반복 실행할 수 있도록 지원하는 반복(loop) 제어문이다.
for 명령문의 형식은 다음과 같다.

for VAR in arg1 arg2 ... argn
do
command1
command2
...
commandN
done

for 제어문의 처리대상이 되어지는 인자들은(arg1,arg2,..,argN) 명시적으로
각각을 제어문상에 지정하거나 또는 다른 명령어의 결과 (예를들면
`cat list`의 형식으로) 또는 화일명 변환문자(*,?,[..])들을 사용하여
지정할 수 있다. for 제어문은 do와 done 사이에 지정되어있는 명령어들을
인자로 지정된 요소들에 대해 각각 실행한다.
do와 done은 C 언어의 함수에서 { 와 }와 같이 경계구별자(Delimiter)의
구실을 한다.

for 구문이 실행되는 과정을 설명하면 다음과 같다.
for 제어문이 실행되었을때 arg1은 구문상에 지정되어 있는
변수(위 형식의 VAR가 이에 해당한다.)에 할당되고, 변수에 할당된 arg1에
대해 do와 done 구문 사이에 지정되어 있는 명령어들이 수행된다.
그후 arg1에 대한 명령어 수행이 완료된후, 연이어 지정되어 있는 인자들에
대해 명령어들이 반복적으로 수행된다.

( 주의 ) do 구문의 끝임을 표시하기 위해 반드시 done을 함께
지정해야 한다.

다음은 for-in-do-done loop 제어문의 사용예이다.

┌─────────────────────────────────┐
│ setuser # 이 프로그램은 중첩된 for loop 사용예를 보여준다.       │
├─────────────────────────────────┤
│ for name in `cat namelist` # cat 명령어의 실행 결과가 순차적으로 │
│ do # name 변수에 할당된후, do 와 done                            │
│ cd /local/usrfiles # 사이의 명령어들이 수행된다.                 │
│ mkdir /home/$name                                                │
│ chown $name /home/$name                                          │
│ for file in *                                                    │
│ do # 중첩하여 지정된 for 구문 사용예                             │
│ cp $file /home/$name                                             │
│ done                                                             │
│ cd /home/$name                                                   │
│ for new in .profile .exrc                                        │
│ do                                                               │
│ chmod 755 $new                                                   │
│ chown $name $new # 또다른 중첩 사용예                            │
│ chgrp other $new                                                 │
│ ls -l $new                                                       │
│ done                                                             │
│ done                                                             │
└─────────────────────────────────┘

2. 쉘 특수변수를 이용한 for loop

이전 강의에서 $*와 $@ 같은 쉘 특수변수에 대한 설명을 했다.
이번 장에서는 이러한 쉘 특수 변수를 이용하여 for loop를
제어하는 예제를 소개한다.

$cat prog1 # prog1 프로그램의 내용을 보면 $* 즉,
for var in $* # 명령어 라인의 인자 갯수만큼의 loop를
do echo $var # 수행하도록 지정하도록 되어 있다.
done # $*는 명령어 인자를 돌려주는 쉘 변수이다.
$prog1 'John Doe' 'Brad Smith'
John # 출력 결과를 보면 명령어 라인의 인자들이
Doe # 보호문자 지정에 관계없이 모두 개별적인
Brad # 인자들로 처리되어 4번의 루프로 처리된
Smith # 것을 볼수 있다.

$cat prog2 # prog2 프로그램은 $@를 사용하여 loop를
for var in $@ # 제어하고 있다. 쉘 변수인 $@는 명령어
do echo $var # 라인내의 인자들을 돌려주는 변수이다.
done
$prog2 'John Doe' 'Brad Smith'
John # 출력 결과를 보면 $@도 $*와 동일한 처리를
Doe # 수행함을 알수 있다.
Brad
Smith

# 이상의 두가지 예를보면 다음과 같은 두가지 의문점이 있을 수 있다.
# 첫째는 명령어 라인에서는 분명 사람 이름을 하나로 처리하기 위해
# 보호문자를 사용하여 묶어 주었는데, 처리 결과를 보면 보호문자가 아무런
# 영향을 미치지 않고 있다는 것이다. 그것은 이전에 강의했던
# 쉘의 명령어 라인 분석 과정때문이다. 명령어 라인에 지정되어 있는
# 보호문자의 처리는 쉘에 의해 수행된다. 즉 쉘은 보호문자로 둘러쌓여있는
# 인자들은 분석하지 않고, 그대로 유지하다가 번역이 끝나는 싯점에서
# 보호문자를 제거하고, 해당 프로그램에 인자를 전달한다. 따라서
# 프로그램은 보호문자가 제거된 상태에서 실행을 하게되어, 이상과 같은
# 결과가 나오게 된것이다. 그러면 사용자가 지정한 형식 그대로
# 프로그램에 인자를 제공하는 방법은 없을까?
# 다음은 이 방식을 보여주는 예제이다.

$cat prog3
for var in "$*" # 이 예에서는 프로그램내의 $* 변수를
do echo $var # 쌍따옴표를 사용하여 보호하도록
done # 지정한 예이다.
$prog3 'John Doe' 'Brad Smith'
John Doe Brad Smith # 결과를 보면 명령어 라인에 지정된
# 인자 모두가 하나의 인자로 처리되고
# 있음을 볼수 있다.
$cat prog4
for var in "$@" # 이번에는 $@를 쌍따옴표를 사용하여
do echo $var # 보호하고 있다.
done
$prog4 'John Doe' 'Brad Smith' # 결과를 보면 사용자가 궁극적으로
John Doe # 원했던 결과로 표시되는 것을 알수가
Brad Smith # 있다.

# 위의 두종류의 예제를 보면 쉘 특수변수인 $*와 $@의 차이를 명확하게
# 구별할 수 있을 것이다. 쉘 특수변수에 대한 사항은 이전 강의를
# 참조하기 바란다.