본문 바로가기

BashShell Programming

챕터 5. 흐름 제어


챕터 5. 흐름 제어

만약 독자 여러분 중 프로그래머이신 분이라면, 지난 챕터를 읽으며 프로그래밍을 하기에 있어 bash의 장점이 무엇인지에 대하여 궁금하셨을 것입니다. 또한 다른 전통적인 언어들에서 제공되고 있는 기능들 또한 지원 가능한 것인지에 대해 궁금하셨을 것입니다. 아마 저희가 빠트리고 설명하지 못한 구멍 if, for, while등과 같은 흐름 제어 구조였다고 생각합니다.

흐름 제어는 프로그램의 특정 부분만을 수행하거나, 특정 부분을 반복하여 수행하거나, 변수 값과 같은 조건에 따라 수행하거나 심지어 명령 구문을 잘못 입력 하였을 경우에 조차 수행이 가능하게끔 만들어줄  있습니다. 이러한 기능을 프로그램 수행의 흐름을 제어해주는 기능이라   있습니다.

지금까지 나왔던 모든  스크립트나 함수에는 이러한 흐름 제어에 대한 내용이 전혀 들어있지 않았습니다. 그저 명령을 순서대로 수행하기만 했을 뿐입니다! bash에도 역시 C 본쉘에서처럼 여러분이 기대하고 있는 모든 흐름 제어 기법을 제공하고 있으며,  이상을 제공하고 있습니다. 우리는 이번 챕터에서 바로이러한 내용들에 대하여 살펴보게  것입니다. 또한  기법들을 사용하여, 지난 챕터까지에서의 과제 내용들을 보강해 나갈 것이며, 새로운 과제들 또한 제공될 것입니다.

이제부터 프로그래머가 아닌 독자들 또한 흐름 제어가 무엇인지를 이해할 수 있게끔 설명을 해나갈 것입니다. 물론 쌩기초부터 차근차근 설명해 나가는 것이 불만족 스러운 프로그래머들 또한 고려하고 있습니다. 아마 이번 장에서 다루게 될 bash의 흐름 제어 기능 중의 일부는 이미 알고 계신 내용들일 수 있습니다흐름 제어 컨셉에 대한 기초적인 내용을 알고 있는 독자라면 이번 챕터를 이해하기에 좀더 수월할 것입니다.

 

 

bash에서 제공되는 흐름 제어 구조는 아래와 같습니다.

 

if/else

조건이 참인지 거짓인지에 따라 구문을 실행한다

for

고정된 횟수만큼 구문을 실행한다

while

조건이 참인 상태를 유지하고 있는 동안 구문을 계속 반복하여 실행한다.

until

조건이 참인 상태가 될 때까지 구문을 계속 반복하여 실행한다.

case

변수의 값에 따라서주어진 구문 중, 해당되는 구문을 실행한다.

 

추가적으로 배시 제공되는 새로운 형태의 흐름 제어 개념이 있습니다.

select

선택지에서 유저가 직접 하나를 선택할 수 있도록 한다.

 

이제 이 부분들에 대하여 좀더 자세히 살펴 보도록 하겠습니다.

 

 

 

 

 

 

5.1 if/else

 

흐름 제어 구조에서 가장 단순한 형태는 bash에서의 if 같은 조건문 입니다. 조건문을 사용하면, 조건의 거짓 여부에 따라서 작업을 수행 할지 말지 결정하거나, 가지의 작업 일부만을 수행하거나 있습니다. 변수 값이나, 파일 특성, 명령 수행 여부 등등을 조건으로서 사용할 있습니다. bash에는 쉘프로그램 작성과 관련된 수많은 빌트인 검사법을 제공하고 있습니다.

 

if 구문은 아래와 같은 문법으로 이루어져 있습니다.

 

if condition

then

    statements

[elif condition

    then statements...]

[else

    statements]

fi

 

(elif else절이 없는)가장 단순한 형태에서는 조건이 참일 경우에만 구문을 수행합니다. else절을 추가하면 조건이 참일 경우에 수행할 구문과, 거짓일 경우에 수행할 구문을 모두 포함한 하나의 세트 형태로 수행할 수 있습니다. 또한 elif(else if의 줄임말)절을 계속적으로 추가함으로써, 더 많은 조건을 사용하여 수행할 구문을 선택하게끔 해줄 수 있습니다하나 이상의 elif를 사용한 경우에서의 else절은 모든 "모든 elif가 거짓일 때"로 생각하면 되겠습니다

 

5.1.1 종료 상태와 Return

C 파스칼과 같은 전통적인 언어들에서 제공되는 조건문과 문법적으로 다른 형태가 하나 있는데, “조건 불린( 거짓) 형태가 아닌, 실제 구문들의 리스트로 이루어져 있다는 것입니다.

C 혹은 다른 언어들로 작성된 모든 UNIX 명령, 스크립트, 함수들은 수행이 완료되면 자신을 호출한 프로세스에 정수 코드를 돌려줍니다. 코드를 종료 상태라고 부릅니다. 일반적으로 0 정상적으로 종료된 상태를 의미하며, 다른 수치들(1 ~ 255) 에러를 포함하고 있음을 의미합니다. <<이는 그렇게 해야 되는 법칙은 아니고, 관례적으로 그렇게 쓰이고 있는 것입니다>>

if if 키워드 뒤에 나열된 구문들 중 가장 마지막에 위치한 구문의 종료 상태를 체크합니다. 일반적으로 하나의 구문만 쓰고 있습니다. 만약 그 종료 상태가 0이라면, 조건이 참인 것으로 결정되며, 그 외의 수치를 가지고 있다면, 거짓 상태인 것으로 결정됩니다. 이는 elif구문(존재한다면)에서도 마찬가지의 방식으로 적용됩니다.

 

따라서 우리는 아래와 같은 형태로도 코드를 작성할  있습니다:

 

if command ran successfully

then

   normal processing

else

   error processing

fi

 

조금  구체적인 설명을 위해, 지난 장에서 보았던 pushd 함수를   발전시켜 보겠습니다:

pushd ()

{

    dirname=$1

    DIR_STACK="$dirname ${DIR_STACK:-$PWD' '}"

    cd ${dirname:?"missing directory name."}

    echo $DIR_STACK

}

이 함수는 유효한 디렉토리를 인자로 요구하고 있습니다. 그렇다면 오류 상태는 어떻게 처리되는지 살펴보겠습니다. 만약 인자가 입력되지 않았다면, 세번째 행에 의해 에러메세지를 출력하고 구문이 종료됩니다. 괜찮아 보입니다.

그러나 인자가 입력은 되었지만 유효하지 않는 디렉토리라면, 함수가 비정상적으로 동작하게 될 것입니다. 지난 챕터에서 읽었던 내용이 떠오르지 않을 수 있으니, 어떠한 일이 벌어지고 있는지 살펴봅시다. cd명령은 실패하게 될 것이며, 디렉토리 또한 변동되지 않을 것입니다. 이것 또한 제대로 동작하는 것으로 보입니다. 하지만 코드 두번째 줄에 의해, 잘못된 경로지만 스택에 입력되게 됩니다. 그리고 마지막 줄에 의해서, 스택에 대한 입력이 성공한 것으로 보여지게 될 것입니다. cd명을을 더 앞쪽에 넣는다해도 마찬가지 입니다. 에러가 있다해도 함수가 종료되지는 않기 때문이지요.

잘못된 디렉토리를 집어넣지 않도록 방지하고 에러 메세지를 발생하도록 해야겠습니다. 이를 어떻게 구현했는지 보십시오:

 

pushd ()

{

  dirname=$1

  if cd ${dirname:?"missing directory name."}    # if cd was successful

  then

      DIR_STACK="$dirname ${DIR_STACK:-$PWD' '}" # push the directory       

      echo $DIR_STACK

  else

      echo still in $PWD.                        # else do nothing

  fi

}

 

cd명령을 if 구문 안에 위치시켰습니다. cd명령이 정상적으로 수행된다면 0을 돌려줄 것이고, 그 다음 두줄의 코드가 수행되어 pushd 작업이 완료될 것입니다. 하지만 cd명령이 실패한다면 종료 상태 1을 돌려줄 것이며, 아무곳으로도 이동하지 않았다는 메세지만을 출력할 것입니다.

잘못된 디렉토리를 체크하는 기능을 제공하기 위해 pushd함수를 약간 수정해 주었음을 주목하십시오. 이제 스택에는 항상 첫번째 디렉토리를 두번 복사하며 동작하기 시작할 것입니다. $PWD의 확장이 새로운 경로로 이동한 뒤에 이루어지기 때문입니다. 이 부분은 다음 섹션에서 수정해볼 것입니다.

일반적으로 빌트인 명령이나, 표준 UNIX 유틸리티들을 사용하면, 적합한 종료 코드를 돌려줍니다. 하지만 직접 작성한 쉘 스크립트나 함수에선 어떨까요? 예를 들어, 빌트인 명령 cd를 오버라이드한 cd함수를 작성해보겠습니다.

 

아래의 코드를 .bash_profile 추가해보십시오.

cd ()

{

    builtin cd "$@"

    echo "$OLDPWD —> $PWD"

}

 

cd 함수는 디렉토리를 옮겨주고, 기존 디렉토리와 새 디렉토리를 출력해주는 기능을 합니다. 대부분의 함수는 빌트인 명령보다 높은 우선순위를 가지고 있기 때문에, 함수내에서 빌트인 cd명령을 호출하는 것임을 명시할 필요가 있습니다. 그렇지 않다면 무한히 함수를 호출하는 상태에 빠지게 될 것입니다.

builtin 명령이 바로 이를 위한 동작을 합니다. builtin은 쉘에 같은 이름의 함수를 무시하고 빌트인 명령을 수행하도록 해줍니다. builtin의 사용법은 간단합니다. 빌트인 명령으로 수행하고자 하는 명령어나 매개변수를 builtin 뒤에 입력해주면 됩니다. 만약 빌트인 명령형태로 존재하지 않는 이름을 넣었다면 builtin: alice: not a shell builtin 같은 식의 에러 메세지를 출력해줍니다.

빌트인 명령 cd의 종료 상태를, 함수 cd에서 똑같이 사용하려고 합니다. 이때에 문제가 하나 있습니다. 종료 상태는 어떤 명령이 수행되면 매번 리셋된다는 것입니다. 따라서 바로 저장해놓지 못하면 종료 상태는 사라지게될 것입니다. 이 함수에서의 경우, echo명령이 수행되는 순간 cd에 대한 종료상태 정보가 사라지게 될 것입니다. (그리고 echo에 대한 종료 상태 정보가 남게 됩니다.)

따라서 cd의 종료 상태를 함수 전체의 종료 상태로 사용하기 위해 따로 저장해 주어야 합니다. 아직까지 설명되지 않은 두가지의 쉘 특성을 사용해야 합니다. 첫번째는, 특수 쉘변수 ? 입니다. 이 변수의 값($?)은 직전에 수행된 명령의 종료 상태를 저장하고 있습니다.

 

cd baddir

echo $?

위의 구문을 수행하면 1 출력할 것이며, 아래의 명령을 수행하면 0 출력할 것입니다.

cd gooddir

echo $?

따라서 종료 코드를 저장하기 위해, cd명령이 수행된 직후에 es=$? 같은 식으로 ?변수의 값을 다른 변수에 할당해 주도록 해야 합니다.

 

 

 5.1.1.1 Return

두번째는 return N 구문입니다. 이 구문을 통하여, 특정 종료 상태 N을 돌려주며 함수를 종료시킬 수 있습니다. N은 사실 옵션이고, 마지막 수행된 명령의 종료 상태가 기본값입니다. return구문을 사용하지 않은 모든 함수는, 가장 마지막 수행된 명령의 종료 상태를 그 함수의 종료 상태로써 돌려줍니다. return은 일반적으로 함수와 source명령을 통해 실행되는 쉘 스크립트 내에서 사용됩니다. exit N구문의 경우는 또 다른데, 복잡하게 구현된 함수더라도 이를 통해 전체 스크립트를 종료시켜 줍니다.

다시 원래 예제로 돌아가, 빌트인 cd 명령 호출이 해당 함수의 가장 마지막 명령이라면 항수가 정상적으로 동작할 것입니다. 하지만 그렇지 못하므로 직접 함수의 상태를 지정해줘야 할 것입니다. 이를 위해 cd명령의 종료 상태를 저장하고, 함수의 종료 상태 코드로서 return할 것입니다. 어떻게 구현했는지 보십시오.

cd ()

{

    builtin cd "$@"

    es=$?

    echo "$OLDPWD —> $PWD"

    return $es

}

두번째 행을 통해 cd명령의 종료 상태를 es 변수에 저장합니다. 그리고 네번째 행에서는 이를 함수의 종료 상태로써 돌려줍니다. 챕터 7에서 cd래퍼에 대한 실체를 살펴볼 것입니다.

종료 상태는 사실 본래의 용도 이외로도  활용할 수 있을 만큼 유용하지는 않습니다. C나 파스칼에서 처럼 함수의 반환 값으로 사용하려 해봐도 실제 동작하지는 않습니다. 다른 함수나 명령을 통해서 이러한 효과를 흉내는 식으로만 사용 가능합니다.

 

5.1.2 종료 코드의 조합

bash 문법 애매한 부분이 하나 있는데, 종료 상태를 논리적으로 조합하여, 한번에 하나 이상의 조건을 검사할 있다는 것입니다.

'statement1 && statement2'라는 문법은 “statement1 실행하고  종료 코드가 0  statement2 실행한다" 의미이며, 'statement1 || statement2'라는 문법은  반대입니다. , " statement1 실행하고  종료 코드가 0 아닐  statement2 실행 한다" 의미입니다.  "if/then"이나 "if not/then' 구문과 비슷한 구조로 보이지만 다릅니다. C프로그래머들은 이해하기 힘들겠지만,  if문 조건절에 사용될 용도로 꼭 필요합니다.

아마 'and' 'or'라고 생각하면 더 쉬울 것입니다. 다음을 생각해보십시오.

if statement1 && statement2

then

    ...

fi

 경우, statement1 실행됩니다. 그리고  종료 코드가 0이면 오류가 없이 수행된 것이므로 statement2 실행됩니다. then 절은 statement2 0 종료 상태를 돌려주면 실행됩니다. 반대로, statement1에서 오류가 발생하면 (0 아닌 종료 코드를 돌려줌) statement2 실행조차 되지 않습니다. 마지막으로 실행된 구문은 오류가 발생된 statement1이되며 then  실행되지 않습니다. 합쳐서 보면, then 절은 statement1 그리고  statement2, 모두가 제대로 실행되었을 때에만 실행된다는 것입니다.

 

비슷하게, 아래와 같은 구문도 살펴봅니다:

if statement1 || statement2

then

    ...

fi

 

statement1 제대로 실행되면 statement2 실행되지 않습니다. 따라서 statement1 마지막 수행된 구문이 되므로 then 절 또한 실행됩니다. 그리고, statement1에서 오류가 발생하면 statement2 실행 될 것이며, then 절이 실행 될지 말지는 statement2 종료 코드에 달려있습다. , statement1 혹은 statement2 둘 중 하나의 구문만 제대로 실행되면 then절 또한 실행됩니다.

bash에서는 !기호(논리 부정)를 사용하여 구문의 상태를 반전시킬 수도 있습니다. 구문 앞에 !를 붙이면, 구문 수행에 실패 했을 시에 0을 돌려주며, 제대로 수행되었을 때에 1을 돌려줍니다. 이번 챕터 마지막 부분이 이에 대한 예제를 살펴볼 것입니다.

간단하게 종료 상태를 검사하는 예를 들어보겠습니다. 파일을 체크하여 특정 두 단어가 있는지 살펴보고, 어떤 단어가 있고 없는지에 대한 내용을 출력해주는 스크립트를 작성해보겠습니다. grep명령을 확용해보면 되겠는데요, 만약 찾는 단어가 있다면 종료 상태 0, 없다면 0이 아닌 수치를 돌려줍니다.

 

filename=$1

word1=$2

word2=$3

 

if grep $word1 $filename || grep $word2 $filename

then

    echo "$word1 or $word2 is in $filename."

fi

grep문 둘 중 하나만 제대로 수행되어도 then절이 수행될 것입니다. 이제 파일에 두 단어 모두가 발견되었을 경우에 대해 생각해봅시다. 아래를 보십시오.

 

filename=$1

word1=$2

word2=$3

 

if grep $word1 $filename && grep $word2 $filename

then

    echo "$word1 and $word2 are both in $filename."

fi

이번 챕터 후반부에서 이러한 논리 연산자들에 대한 예제를  살펴볼 것입니다.

 

5.1.3 조건 검사

if 구문에서 검사할 있는 것은 종료코드 한가지 뿐입니다. 그것이 명령이 정상적으로 종료했는지에 대한 테스트만이 가능하다는 의미는 아닙니다. bash에서는 [...]구조를 통하여 훨씬 다양한 경우의 조건들을 검사할 있습니다. << [...] 빌트인 명령 test 같은 의미입니다. [string1=strng2 ] 마찬가지로 test string1=string2 사용해도 됩니다.>>

구조를 통해, 파일의 수많은 속성들을 검사해볼 수도 있으며(파일이 실제로 존재하는지, 파일의 타입은 무엇인지, 소유와 권한은 어떻게 되는지 등등) 파일을 비교하여 최신의 파일을 찾을 수두 있고, 문자열 비교를 하는것도 가능합니다.

condition ] 형태로 사용되며 다른 구문들과 똑같이 사용됩니다. 단지 구문이 하는 일은, 조건을 검사하여 참인지 아닌지에 대한 여부를 돌려 주는 것이 전부일 뿐입니다. ( “[“ 뒤와 “]” 칸씩은 반드시 공백을 둬야 합니다) if구문의 문법과 맞습니다.

 

조건 검사를 하려면 다른 여느 구문처럼 [ condition ] 사용하면 된다. 하지만  가지 다른 점은  condition 참인지 거짓인지 구분하는 종료 코드를 돌려주는게 조건 검사의 전부라는 것이다. [ ] 사이에 반드시 공백 문자가 하나식 있어야 한다는 점도 주의하기 바란다. if 구문에서 사용하면 궁합이  맞을 것이다.

 

5.1.3.1 문자열 비교

여러가지 종류의 연산자들을 대괄호로 묶어줄 수 있습니다. 우선 문자열 비교 연산자부터 살펴볼 것입니다. 5.1을 보십시오.(“같거나 큰”, “같거나 작은에 해당하는 연산자가 없다는 것을 주의 깊게 봐주세요) 표에서 str1 str2는각각 문자열 값을 니타냅니다.

 5.1. 문자열 비교 연산자

연산자

참이 되는 경우

str1 = str2 

str1  str2 같은 경우

str1 != str2

str1  str2 다른 경우

str1 <  str2

str1  str2보다 작은 경우

str1 >  str2

str1  str2보다 경우

-n str1

str1  null 아닌 경우(문자열의 길이가 0보다 )

-z str1

str1  null 경우 (문자열의 길이가 0)

[3]

연산자들만을 이용해 popd 함수를 개선해보겠습니다. 기존 popd 스택이 비어있을때 비정상적으로 동작했습니다. popd 함수를 다시 보겠습니다.

popd ()

{

    DIR_STACK=${DIR_STACK#* }

    cd ${DIR_STACK%% *}

    echo "$PWD"

}

스택이 비어 있을 경우 ${DIR_STACK%% *} 보면 $DIR_STACK null 문자열  것을 확인할  있습니다. 이 경우에는 홈 디렉토리로 이동하게 되므로, 그대신 에러 메세지를 출력하고 다른 경로로 이동하지 않게끔 만들어보겠습니다.

이를 위해, 스택이 비어있는지 테스트해봐야겠습니다. 예를 들어 $DIS_STACK null인지 아닌지를 살펴보면 되겠습니다. 다음이 한가지 방안이 되겠습니다.

popd ()

{

    if [ -n "$DIR_STACK" ]; then

        DIR_STACK=${DIR_STACK#* }

        cd ${DIR_STACK%% *}

        echo "$PWD"

    else

        echo "stack empty, still in $PWD."

    fi

}

조건부를 살펴보면,  $DIR_STACK  따옴표로 묶었으므로, $DIR_STACK 확장될  하나의 단어로 취급될 것입니다. 만약  따옴표로 묶지 않는다면 $DIR_STACK 따로따로 확장되므로, test시에 인자가 너무 많다는 메시지를 출력할 것입니다.

$DIR_STACK  따옴표로 묶는 더 중요한 이유가 있습니다. 가끔 검사되는 변수가 확장되지 않는 경우가 있기 때문입니다. 이 예제에서라면 [ -n ]과 같은 형태가 될 것이며 결과는 참이 될 것입니다. 큰 따옴표로 묶어주었기에, 확장될 수 없는 경우에도 빈 문자열로 확장시켜주게 됩니다. (이 경우 [ -n “” ] 와 같은 형태가 됩니다)

또한 then if와 분리된 행에 넣지 않고 같은 행에 함께 넣어주었습니다. 이때에 if위에 세미콜론을 넣어주며, 이는 bash쉘의 표준 분리 문자입니다.

-n이 아닌 연산자를 사용할 수도 있습니다. 예를 들면 -z를 사용한 뒤에 then절 내용과 else절 내용을 뒤바꾸어 사용해도 되겠습니다.

 

이제 지난 챕터에서 작성했던 스크립트를 정리해보겠습니다. 과제 4-1 highest 스크립트의 에러처리법을 수정하겠습니다. 해당 스크립트의 코드는 아래와 같습니다.

filename=${1:?"filename missing."}

howmany=${2:-10}

sort -nr $filename | head -$howmany

 

만약 첫 번째 인자(파일명)를 빠트렸다면 hightest: I: filename missing이라는 메시지를 출력하게됩니다. 이제 우리는 이를 좀더 일반적으로 사용되는메세지로 바꿔볼 것입니다. 또한 일반적인 UNIX명령어처럼 보이게 하기 위하여 대쉬를 사용하여 옵션 인자를 입력받도록 해줄 것입니다.

if [ -z "$1" ]; then

    echo 'usage: highest filename [-N]'

else

  filename=$1

  howmany=${2:—10}

  sort -nr $filename | head $howmany

fi

우선 $howmany앞에 있던 대시를 변수 확장 부분인 ${2:--10} 안으로 옮겼습니다.

모든 코드를 if-then-else형태로 묶어주는 것이 좋은 프로그램 습관이긴 하지만, 스크립트를 작성할 때에는 에러 체크가 어려울 수도 있으므로, 프로그래밍에서는 아래와 같은 형태가 더욱 일반적입니다.

if [ -z "$1" ]; then

    echo 'usage: highest filename [-N]'

    exit 1

fi

 

filename=$1

howmany=${2:—10}

sort -nr $filename | head $howmany

여기서 exit구문은 프로그램 수행이 정상적으로 이루어졌는지 아닌지를 알려줍니다.

 

= 연산자에 관한 예제는, 과제 4-에서 다뤘던 그래픽 유틸리티를 생각해볼  있겠습니다. 이는 .pcx(원본 파일) 끝나는 파일명 입력받아 .gif(출력 파일) 끝나는 파일로 이름을 변환하는 과제였습니다. 여기에 다른 종류의 파일들도 변환해줄 있게 되면 페이지 등에 사용하기 좋을 것입니다. PCX 외에 자주 사용되는 파일로는  XPM (X PixMap), TGA (Targa), TIFF (Tagged Image File Format),  JPEG (Joint Photographics Expert Group)등이 있습니다.

그래픽 변환 프로그램이 실제 어떻게 파일 변환을 처리되는지에 대해서는 언급하지 않을 것입니다. 대신 인터넷에서 구할 수 있는 무료 툴을 사용하기로 하겠습니다. NetPBM이나 Independent JPEG Group등에서 나온 변환 유틸리티를 사용하면 되겠습니다.

이 유틸리티가 어떻게 동작하는지에 대한 상세한 부분은 고민하지 마시고, bash쉘에서 파일명을 어떻게 처리하고 변환 유틸리티를 호출하는지에 대해서만 고려하십시오. 이 경우에, 변환 유틸리티들이 파일명을 인자로서 입력 받는다는 것과 변환된 결과받을 표준 출력으로 출력해준다는 것만 알면 충분합니다. 30가지가 넘는 수많은 변환 프로그램들에 대한 다 사용하지 않고. NetPBM의 고유 형식인 PNM(Portable Anymap file) 변환할 것입니다. 각각의 그래픽 파일 포맷마다 PNM파일로 바꿔주는 유틸리티가 따로 있습니다.

따라서 우리가 작성할 스크립트는, 파일명에 따라 올바른 유틸리티를 선택해주는 기능이 필요하며, PNM형식에서 다시 GIF 변환하는 작업을 해주면 됩니다.

filename=$1

extension=${filename##*.}

ppmfile=${filename%.*}.ppm

outfile=${filename%.*}.gif

 

if [ -z $filename ]; then

    echo "procfile: No file specified"

    exit 1

fi

 

if [ $extension = gif ]; then

    exit 0

elif [ $extension = tga ]; then

    tgatoppm $filename > $ppmfile

elif [ $extension = xpm ]; then

    xpmtoppm $filename > $ppmfile

elif [ $extension = pcx ]; then

    pcxtoppm $filename > $ppmfile

elif [ $extension = tif ]; then

    tifftopnm $filename > $ppmfile

elif [ $extension = jpg ]; then

    djpeg $filename > $ppmfile

else

    echo "procfile: $filename is an unknown graphics file."

    exit 1

fi

 

ppmquant -quiet 256 $ppmfile | ppmtogif -quiet > $outfile

 

rm $ppmfile

 

${filename%.*}는 지난 장에서도 살펴 보았듯이 filename에서 확장명 부분을 삭제해 줄 것이며, ${filename##*} 파일명 부분을 삭제해줄 것입니다.

변환 작업에 사용될 프로그램이 선택되면, 스크립트는 해당 유틸리티를 실행하고, 출력 값을 임시 파일로 저장할 것입니다. 끝에서 두 번째 행에서 다시 임시 파일을 입력 값으로 받아 GIF형태로 변환하여 줄 것입니다. 임시 파일은 삭제됩니다. 원본 파일이 GIF파일이라면 아무런 작업도 진행하지 않고 프로그램을 종료시킬 것입니다.

 

이 스크립트에도 역시 몇몇 문제가 있습니다. 이러한 부분들은

 

5.1.3.2 파일 속성 검사

조건문에서 사용할 있는 다른 종류의 연산자들로 파일 속성을 검사하는 것들이 있습니다. 20여가지의 연산자들이 존재합니다. 챕터에서는 가장 일반적으로 사용되는 것들만 살펴볼 것입니다. 스티키 비트, 소켓, 파일 디스크립터와 같은 시스템 해커들이나 관심 가질만한 흔히 없는 내용이라 생각합니다. 부록 B에는 모든 연산자들이 들어있습니다. 5.2에는 우리가 살펴볼 것들이 나열되어 있습니다.

 5.2. 파일 속성 연산자

연산자

참이 되는 경우

-d file

파일이 존재하며 디렉토리일 경우

-e file

파일이 존재할 경우

-f file

파일이 존재하며 일반 파일일 경우

-r file

읽기 권한이 있는 경우

-s file

파일이 존재하며 파일이 아닐 경우

-w file

쓰기 권한이 있는 경우

-x file

실행 권한이 있거나, 디렉토리일 경우 검색 권한이 있는 경우

-O file

해당 파일의 소유자인 경우

-G file

해당 파일의 소유 그룹에 속해있는 경우

file1 -nt  file2

file1 file2보다 최신의 파일인 경우

file1 -ot  file2

file1 file2 보다 오래된 파일인 경우

[6]

예제로 들어가기 전에 다시 한번 말씀드리자면, 조건절에서 [ ] 묶문 부분은 논리 연산자 && ||또한 적용할 있으며,  직전 섹션에서 내용을 확인해 보았었습니다. 다음은 예제입니다.

if [ condition ] && [ condition ]; then

이는 또한 다음과 같이 논리 연산자를 사용하여 쉘명령과 조건 구문을 조합하는 형태로도 사용 가능합니다.

if command && [ condition ]; then

    ...

조건 구문의 값을 부정할 때에는 마찬가지로 느낌표(!)를 사용하면 됩니다. !expr expr이 거짓이어야 참이 됩니다. 또한 조건 연산자를 괄호로 묶고(이때에 이스케이프 문자인 역슬래쉬를 사용하여 특수 문자로 처리하지 말아야 합니다) 아직 등장하지 않은 -a (AND), -o (OR)와 같은 논리 연산자를 사용함으로써 논리적으로 복잡하게 연결하여 사용 가능합니다.

-a -o 연산자는, && ||연산자와 종료 상태를 다루는 방법이 비슷하지만, -a -o test 조건 구문 안에서만 사용 가능합니다.

이제 파일 연산자중 두가지, 논리 연산자와 문자 연산자를 어떻게 사용하여 pushd함수에서 스택이 중복되는 현상을 방지할 것인지 살펴보겠습니다. cd가 인자로 받은 디렉토리가 유효한 디렉토리인지 판단하게 할 것이 아니라, 잘못된 경우 잘못된 종료 상태를 돌려주게 하여 우리가 직접 체크하도록 하겠습니다. 아래의 코드를 봐주십시오.

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

}

조건 구문에 따르면 $1 인자가 널이 아니고(-n), 디렉토리이고 (-d),  변경 권한이 있을때(-x) 참이 됩니다. 제일 먼저  인자가 존재하지 않는($dirname null) 상황에 대해서부터 따져보게 될 것이며, 이 때에 나머지 조건절은 아예 실행조차 되지 않는다는 것을 기억해두십시오. 다음과 같이 값을 넣을 수도 있기 때문에 매우 주의해야 합니다.

if [ \( -n "$dirname"\) -a  \( -d "$dirname" \) -a \

         \( -x "$dirname" \) ]; then

두번째 조건이 null값이면, 에러메세지를 출력후 전체 함수가 종료될 것입니다.

다음은 파일 연산자를 설명해줄 더욱 훌륭한 예제입니다.

과제 5-1

ls -l 명령과 같은 정보를 출력해주는 스크립트를 작성하십시오 . , 좀더 유저 친화적인 형태로 출력되어야 합니다.

 

과제는 지루한 코딩을 요구하겠지만, 파일연산자의 사용을 정확히 보여줄 것입니다.

if [ ! -e "$1" ]; then

    echo "file $1 does not exist."

    exit 1

fi

if [ -d "$1" ]; then

    echo -n "$1 is a directory that you may "

    if [ ! -x "$1" ]; then

        echo -n "not "

    fi

    echo "search."

elif [ -f "$1" ]; then

    echo "$1 is a regular file."

else

    echo "$1 is a special type of file."

fi

if [ -O "$1" ]; then

    echo 'you own the file.'

else

    echo 'you do not own the file.'

fi

if [ -r "$1" ]; then

    echo 'you have read permission on the file.'

fi

if [ -w "$1" ]; then

    echo 'you have write permission on the file.'

fi

if [ -x "$1" -a ! -d "$1" ]; then

    echo 'you have execute permission on the file.'

fi

 스크립트의 이름을 fileinfo라고  것입니다.  스크립트는 아래와 같이 동작합니다.

·       첫째 조건은 인자로 받은 파일이 존재하지 않는지 (느낌표를 사용했으므로 부정이며, 느낌표 다음에 공백 문자가 있다는 것에 유의해 합니다.) 분입니다. 파일이 존재하지 않는 경우에는 오류메시지를 출력하고 오류 상태를 돌려주며 종료합니다.

·       둘째 조건은 인자로 받은 파일이 디렉토리인지 검사합. 그런 경우에 첫째 echo 메시지 일부를 출력하는 역할을 합니. 기억할 것은 -n 옵션을 사용했으므로  끝에 LINEFEED 출력하지 않는다는 것입니다. 그리고 현재 사용자에게 디렉토리 검색 권한이 있는지 검사합니다. 권한이 없는 경우에는 출력할 메시지 일부에 'not' 추가합니다. 제대로  메시지는 'search'라는 단어와LINEFEED 추가됩니다.

·       elif절은 인자로 받은 파일이 일반 파일인지 검사하고 일반 파일이면 메시지를 출력합니.

·       else절을 사용한 이유는 최근 유닉스 시스템에는 소켓, 디바이스, FIFO파일등 아주 다양하고 특별한 파일 종류가 있기 때문입니다. 일반적으로 사용자가 이런 파일을 자세히  필요는 없습니다.

·       다음 조건은 인자로 받은 파일이 현재 사용자의 소유인지( 파일의 소유자 ID 현재 사용자의 로그인ID 같은지)검사하는 부분입니다. 사용자의소유라면 소유하고 있다는 메시지를 출력합니다.

·       다음  조건은 해당 파일의 읽기와 쓰기 권한이 있는지 검사하는 부분입니다.

·       마지막 조건은 해당 파일을 실행할  있는지, 다시 말해 실행 권한이 있는지 그리고 디렉토리가 아닌지 검사하는 부분입니다. 디렉토리인 경우에는 실행권한이라는 것이 디렉토리 검색 권한을 의미합니다.  스크립트를 보면 검사 조건을 대괄호로 묶어서 작성하지 않았습니다. 바로 연산자의 우선순위를 사용했기 때문입니다. 간단히 말해 연산자 우선순위란 셀이 연산자를 처리하는 순서를 가리킵니다. 수학에서 덧셈 곱셈 뺄셈 나눗셈 등의 우선순위와 차이가 전혀 없듯이, 여기서는 [ -x "$1" -a ! -d "$1" ]  우선순위가 [\( -x "$1" \) -a \( ! -d "$1" \) ] 우선순위와 같습니다. 파일 검사를 먼저 하고 부정을 나타내는 느낌표를 처리하고  다음에 AND OR 검사를 합니다.

 

fileinfo 결과물의 예입니다. 우선 현재 디렉토리에서 ls -l을 입력했을 때에 다음 내용이 출력됩니다.

-rwxr-xr-x   1 cam      users        2987 Jan 10 20:43 adventure

-rw-r--r--   1 cam      users          30 Jan 10 21:45 alice

-r--r--r—   1 root     root        58379 Jan 11 21:30 core

drwxr-xr-x   2 cam      users        1024 Jan 10 21:41 dodo

 

alice core는 일반 파일, dodo 디렉토리, adventure  스크립트입니다. 다음은 fileinfo adventure에 대한 결과입니다:

adventure is a regular file.

you own the file.

you have read permission on the file.

you have write permission on the file.

you have execute permission on the file.

 

fileinfo alice에 대한 결과입니다:

alice is a regular file.

you own the file.

you have read permission on the file.

you have write permission on the file.

 

fileinfo dodo 에 대한 결과입니다:

dodo is a directory that you may search.

you own the file.

you have read permission on the file.

you have write permission on the file.

 

fileinfo core에 대한 결과입니다:

core is a regular file.

you do not own the file.

you have read permission on the file.

 

5.1.4 정수 조건

bash쉘에서는 수학 조건에 대한 검사 또한 제공하고 있습니다. 이는 문자열 비교에서의 <, >와는 다릅니다. 문자열 비교시, , 사전적인 비교시에 “p” “ox”보다 값인 것처럼 6 57보다 높은 값입니다. 수학적인 비교시에는 물론 반대가 정답이 것입니다.

정수 연산자는 5.3 정리되어 있습니다..

 5.3. 정수 검사 연산자

검사

비교

-lt

작을 경우

-le

같거나 작을 경우

-eq

같을 경우

-ge

같거나 경우

-gt

경우

-ne

같지 않을 경우

연산자들은 정수 변수와 함께 사용될 경우가 많으며, 다음 챕터에서 많이 보게될 것입니다. 다른 종류의 조건 검사와 함께 정수의 비교를 해야할 경우에 반드시 필요합니다.

하지만 bash쉘에서 정수들로만 이루어진 조건 검사시에는 별도의 문법을 사용하고 있습니다. 방식이 더욱 효과적이며, 위에 제공간 정수 검사 연산자보다 우선시하여 사용하셔야 것입니다. bash쉘에서의 정수 조건에 대해서는 다음 챕터에서 다시 살펴볼 것입니다.

 

 

  5.2 for

이전 스크립트에 비하여 확연히 발전시킬 있는 부분이 하나 있습니다. 하나의 파일이 아닌 여러 파일에 동시 접근하는 것입니다. -e -d같은 검사는 하나의 인자만을 사용합니다. 따라서 각각의 파일에 마다 코드를 호출해주어야할 필요가 있습니다.

이를 수행하기 위하여 (bash에서 제공 되는 방법은 많지만) 루프 구조를 사용할 것입니다. 가장 간단하고 널시 사용되고 있는 루프 구문은 for 루프 구문입니다. fileinfo 스크립트를 개선하기 위하여 for 구문을 사용할 것입니다.

for 구문은 고정된 횟수만큼 구문을 반복 수행할때 사용됩니다. 매번 코드가 반복 수행될 마다, 특수 변수인 루프 변수가 매번 다른 값으로 설정됩니다. 이러한 식으로 매번 반복 수행시 마다 약간은 다른 작업을 수행하게 됩니다.

for 구문은 C 파스칼에서 제공되는 for구문과 어느정도 유사성을 갖고 있습니다. 가장 차이점은 배시의 for구문은 반복 수행할 횟수나 범위를 지정할 수가 없다는 것입니다. 대신 배시에서는 고정된 값의 목록을 이용하게 됩니다. , 구문을 10 수행하도록 하게끔 하는 파스칼식의 for 구조를 이용할 수가 없습니다.

for x := 1 to 10 do

begin

    statements...end

배시에서는 고정된 값의 목록을 이용하게 됩니다. , 구문을 10 수행하도록 하게끔 하는

(이런 식으로 사용하려면 배우게될 while구문을 사용해야 합니다. 혹은 정수 산술형을 이용할 있습니다. 내용은 챕터 6 등장합니다.)

하지만 for 구문은 명령행에서의 수행시나 여러개의 파일을 다루는 식의 작업에서는 최선이라 있습니다. (예를 들면, 주어진 디렉토리 안의 모든 파일에 대한 작업) 이제 이러한 내용에 대한 예제들을 하나씩 살펴볼 것입니다. 우선 for구문의 문법을 살펴보겠습니다.

 

for name [in list]

do

    statements that can use $name...

done

list name 목록입니다. (list 생략되었다면, 따옴표로 묶인 상태의 명령행 인자들을 의미하는, 기본값 "$@"으로 지정됩니다. 하지만 in list 항상 명시적으로 입력하도록 합시다) 다음의 과제를 해결하기 위해 두개의 간단한 목록들을 사용해 것입니다.

과제 5-2

과제 4-4에서는 패턴 매칭과 대체 기능을 통해 PATH내의 디렉토리 목록을 한줄로 표현하는 작업을 했었습니다. 아쉽게도 과거 버전의 배시에서는 그러한 패턴 연산자를 제공하지 않습니다.  따라서 보편적인 listpath 스크립트를 작성할 것입니다. 추가적으로 각각의 디렉토리에 대한 권한이나 수정 시간과 같은 정보들을 출력하도록 해야합니다.

가장 쉽게 이를 해결하는 방법은 챕터4 등장했던 IFS변수를 바꾸는 것일 겁니다.

IFS=:

 

for dir in $PATH

do

    ls -ld $dir

done

이렇게 하면 IFS PATH에서 구분 문자로 사용되고 있는 콜론으로 설정됩니다. for구문은 PATH에서 콜론으로 구분되어진 모든 부분에서 동작하게 됩니다. ls 디렉토리명과 관련 정보들을 출력해줍니다.  -l 매개변수를 사용하면 ""형태로 출력해주며, -d 내용을 제외한 디렉토리만을 출력하도록 합니다.

이를 사용하면 ls 출력해주는 ls: /usr/TeX/bin: No such file or directory 같은 에러 메세지를 보게될 수도 있습니다. 이는 PATH 적힌 디렉토리가 실제로는 존재하지 않음을 의미합니다. 따라서 listpath 스크립트를 수정하여 PATH변수에 존재하지 않는 디렉토리가 있는 것은 아닌지 체크하도록 하겠습니다.

IFS=:

 

for dir in $PATH; do

    if [ -z "$dir" ]; then dir=.; fi

 

    if ! [ -e "$dir" ]; then

        echo "$dir doesn't exist"

    elif ! [ -d "$dir" ]; then

        echo "$dir isn't a directory"

    else

        ls -ld $dir

    fi

done

The foregoing illustrated a simple use of for, but it's much more common to use for to iterate through a list of command-line arguments. To show this, we can enhance thefileinfo script above to accept multiple arguments. First, we write a bit of "wrapper" code that does the iteration:

체크하도록 하겠습니다.

루프 구문이 시작되면 제일 먼서 $dir 길이가 0인지 살펴보게 됩니다. (PATH :: 같은 값이 있을 경우입니다.) 만약 0이라면 현재 디렉토리를 설정합니다. 그리고 나서는 해당 디렉토리가 존재하지 않는지 체크하게 됩니다. 만약 존재하지 않는다면, 이에 적합한 메세지를 출력해줍니다. 아니라면 해당 파일이 디렉토리인지 아닌지 체크합니다. 아니라면 아니라고 출력합니다.

다음은 for 사용하는 간단한 예제입니다만, 일반적으로 for구문은 명령행 매개변수들의 목록을 반복하는데 사용됩니다. 이제 여러개의 인자를 받는 식으로 fileinfo 스크립트를 개선해보겠습니다. 첫째로, 실제 반복 수행하게 "래퍼"코드를 작성하겠습니다.

for filename in "$@" ; do

    finfo "$filename"

    echo

done

그리고, 원본 스크립트를 finfo라는 함수로 변환하겠습니다.

finfo ()

{

    if [ ! -e "$1" ]; then

        print "file $1 does not exist."

        return 1

    fi

    ...

}

for루프 구문과 위에 나온 함수를 포함한 스크립트를 완벽하게 작성하자면, 어떠한 순서대로 작성을해도 상관은 없지만, 함수 정의를 먼저 쓰는것이 좋은 프로그래밍 스타일입니다.

fileinfo 스크립트는 다음과 같이 동작합니다: for 구문내에서, “$@” 위치 변수들의 목록을 뜻합니다. 각각의 매개변수들은 루프 본문 내에서는 filename이라는 변수에 할당되어 동작합니다. , finfo함수는 $filename변수의 첫번째 위치변수($1)로써 호출되게 되는 것입니다. finfo 다음으로 호출되는 echo명령은 각의 파일 정보 사이에 줄을 남기게끔 해줍니다.

앞쪽의 예제에 나왔던 것과 같은 디렉토리 정보들에 대하여 fileinfo * 같은 식으로 명령을 실행했을 때에 결과 값은 다음과 같습니다.

adventure is a regular file.

you own the file.

you have read permission on the file.

you have write permission on the file.

you have execute permission on the file.

 

alice is a regular file.

you own the file.

you have read permission on the file.

you have write permission on the file.

 

core is a regular file.

you do not own the file.

you have read permission on the file.

 

dodo is a directory that you may search.

you own the file.

you have read permission on the file.

you have write permission on the file.

 

다음은 for구문의 또다른 주요 사용법을 보여주는 프로그래밍 과제입니다.

과제 5-3

ls -R옵션을 사용함으로써 입력받은 디렉토리 밑의 모든 내용을 출력하는 것이 가능합니다. 하지만, 이는 디렉토리 구조를 파악하는데에 있어 좋은 방법이 되지는 못할 것입니다. 모든 파일과 디렉토리를 한줄 한줄 출력해주기 때문입니다. 따라서 우리는 디렉토리 구조를 염두에 두고, 적은 숫자의 서브디렉토리만을 출력해주는 스크립트를 작성할 것입니다.

 

다음과 같은 식으로 결과가 출력 되기를 원하실 것입니다.

.

        adventure

                aaiw

                        dodo

                        duchess

                        hatter

                        march_hare

                        queen

                        tarts

                biog

                ttlg

                        red_queen

                        tweedledee

                        tweedledum

        lewis.carroll

각각의 열은 디렉토리 레벨을 의미합니다. 값의 아래,오른쪽으로 위치한 값들은 해당 디렉토리에 속해있는 파일과 디렉토리입니다. 자신의 우측에 더이상 값이 없다는 것은 해당 값이 파일임을 의미합니다. 예제에서 adventure디렉토리와 lewis.carroll파일은 현재 디렉토리에 위치하고 있습니다. aaiw, ttlg디렉토리와 biog파일은 adventure 디렉토리 하위에 위치합니다. TAB 이용하여 간단하게 컬럼을 맞춰줄 있습니다.

디렉토리 계층 구조를 추적할 필요가 있습니다. 이를 쉽게 구현하기 위해 recursion이라는 프로그래밍 기법을 사용할 것입니다. recursion 자기 자신으로부터 값을 참조하는 방법입니다. 우리는 자기 자신의 코드를 다시 참조하게끔 것입니다. 예를 들어 다음 tracedir 스크립트를 자신의 홈디렉토리에 작성해두십시오.

 

file=$1

echo $file

 

if [ -d "$file" ]; then

    cd $file

    ~/tracedir $(ls)

    cd ..

fi

우선 첫번째 위치 변수를 복사하고 출력합니다. 그리고는 그것이 디렉토리인지를 체크합니다. 만약 디렉토리가 맞다면 cd명령을 통하여 해당 경로로 이동하고, 다시 디렉토리 내에서 tracedir 스크립트를 호출합니다. 만약 첫번째 위치 변수가 디렉토리라면, 새로운 디렉토리에서 새로운 쉘이 다시 새로운 스크립트를 호출하게 것입니다. 그러면 이전 스크립트는, 새로운 스크립트가 값을 반납할 때까지 기다리게 됩니다. 값을 받고나면 이전 스크립트는 다시 cd스크립트 실행을 이어나가게 되며, 와중에cd..명령을 통해 단계 상위 디렉토리로 이동하게 됩니다. 이러한 동작이 tracedir스크립트에 포함되어 있는 모든 디렉토리들에서 발생하게 됩니다. recursion 첫번째 위치 변수가 디렉토리가 아니게 되었을때야 동작을 멈추게 됩니다.

위에 나온 디렉토리 구조상의 adventure 디렉토리 내에서 명령을 실행하면 다음과 같은 값을 출력할 것입니다.

adventure

aaiw

dodo

dodo 파일이므로 스크립트가 종료됩니다.

스크립트에는 몇가지 문제점이 있지만, 이번 과제에서 원했던 솔루션들은 여기까지입니다. 이러한 문제점들에는 우선 효율이 매우 낮다는 것을 있겠습니다. 매번 스크립트가 호출될 마다, 새로운 쉘이 생성되게 됩니다. 이는 스크립트를 함수로 변경함으로써 해결하게 것입니다. 챕터 4에의 내용을 되새겨 보시면, 함수는 쉘의 일부로 동작한다 하였었습니다.  그리고 TAB 통해 공간을 맞춰준 것도 문제가 되겠습니다. 이는 초기화 스크립트나 함수를 사용한 위에, 해당 스크립트나 함수를 recursive하게 호출하여 사용하는 식으로 해결이 가능합니다. 아래를 살펴보십시오.

recls ()

{

    singletab="\t"

 

    for tryfile in "$@"; do

        echo $tryfile

        if [ -d "$tryfile" ]; then

            thisfile=$tryfile

            recdir $(command ls $tryfile)

        fi

    done

 

    unset dir singletab tab

}

우선 echo명령(7 입출력과 명령행 처리 에서 echo 모든 옵션과 형식을 자세히 설명할 것이다) TAB 문자를 제공하는 필요한 변수를 설정한다. 다음은 함수에 제공된 인자를 반복하고 결과를 출력한다. 인자가 디렉토리인 경우에는 순환 루틴을 호출하여 파일 목록을 제공한다. 이제 새로운 명령인 command 설명할 차례가 같다. command 셀의 내장 명령으로서 함수와 앨리어스 검색을 무력화하는 사용한다. 경우에는 ls 명령이 명령 검색 경로인 PATH 있고 함수가 아니라는 것을 확실히 하는 사용한다(7 장에서 command 자세히 설명했으니 참고하기 바란다). 모든 과정이 끝나면 사용한 변수들을 정리해야 한다.

우선 echo명령에서 TAB문자를 호출할때에 이용할 변수를 설정합니다. (echo 대한 모든 옵션과 형식들은 챕터7에서 다시 설명될 것입니다.) 그리고는 함수에 사용된 각각의 매개 변수에 대하여 반복 수행이 되며 결과를 출력할 것입니다. 만약 변수가 디렉토리라면, recursive 루틴을 호출하여 ls명령을 통해 파일 목록을 제공합니다. 시점에서 새로운 명령어를 하나 소개하겠습니다. command 명령입니다. 이는 빌트인 명령으로써, 함수와 별칭을 사용하지 않게끔 해줍니다. 경우에는, ls명령이 PATH 지정되어있는 경로에 있는 명령중의 하나이며, 함수가 아니라는것을 확실히 하기 위해 사용되고 있습니다. (command 명령에대한 자세한 내용은 챕터7 나옵니다.) 진행 과정이 완료되면, 사용되었던 모든 변수들을 초기화 합니다.

이제 이전의 스크립트를 더욱 발전시킬 있을 것입니다..

 

recdir ()

{

    tab=$tab$singletab

 

    for file in "$@"; do

        echo -e $tab$file

        thisfile=$thisfile/$file

 

        if [ -d "$thisfile" ]; then

            recdir $(command ls $thisfile)

        fi

 

        thisfile=${thisfile%/*}

    done

 

    tab=${tab%\t}

}

 

 

이제 이전의 스크립트를 더욱 발전시킬 있을 것입니다..

함수가 호출될 때마다, 매개 변수로 전달된 모든 파일들에 대하여 recdir함수가 동작할 것입니다. 각각에 대하여 파일명을 출력해 것이며, 파일이 디렉토리라면 해당 디렉토리안의 내용들을 변수로 삼아 함수 자기 자신을 한번 호출할 것입니다. 두가지 주의해야 사항이 있는데, TAB 몇번 사용할 것인지와, 현재 디렉토리의 경로명이 것입니다.

매번 디렉토리 계층 구조를 타고 내려갈 때마다 TAB문자를 추가하기 위해, recdir 호출될 때마다 TAB 변수 tab 저장해두도록 합니다. recdir 종료될 때에 상위 디렉토리로 이동하게 되며, 그때엔 TAB 비워줘야 하겠습니다. 처음에는 tab 설정되어 있지 않으므로 recdir 처음으로 호출되 때에 tab에는 하나의 TAB 설정됩니다. 계층을 내려가게되면 recdir 한번더 호출되며 TAB 개수가 늘게 됩니다. tab 전역변수이므로 recdir 매번 호출되고 종료될때마다 TAB값이 늘었다 줄었다 것입니다. echo명령의 -e옵션은 이스케이프 처리된 문자를 인식하게 해주며, 여기서 TAB문자는 \t 인식됩니다.

이번 버전에서는 cd 사용하여 디렉토리를 이동하지 않았습니다. , ls명령에 제공되는 파일명이  디렉토리 계층구조를 따라 내려가며 상대경로 방식으로써 제공되었다는 것입니다. 이를 위해 초기화 루틴에서 thisfile변수를 매번 사용된 디렉토리 명으로 설정합니다. 그리고 변수를 recursive 루틴에서 상대 경로명을 담는데 사용됩니다. 루프구문의 단계에서 thisfile변수는 현재 검사된 파일명을 thisfile 내용 뒤에 붙여주며, 루프구문이 종료될때에 내용을 삭제합니다.

어쩌면 스크립트의 동작 방식을 수정하거나, 출력 형태를 개선해보고자 하는 분들도 있을 것입니다. 다음 내용들을 한번 생각해보십시오.

1. 이번 버전에서는 biog 파일인지 디렉토리인지를 알아낼 방법이 없었습니다. 비어있는 디렉토리가 일반 파일들과 다르게 보이지 않았기 때문입니다. 디렉토리명을 출력할 때에는, 디렉토리명 끝에 / 문자를 붙이도록 해보십시오.

2. 코드를 수정하여 최대 8단계의 서버디렉토리 까지만 내용을출력하게끔 변경해보십시오. ( 정도가 화면에 출력될 있는 정량입니다.) 힌트 : TAB 어떤식으로 사용되어왔는지 생각해보십시오.

3. 각각의 디렉토리 사이에 공백을 넣고, 대쉬 줄을 시용하여 아래와 같은 형태로 출력되게 변경해보십시오.

          .

          |

          |-------adventure

          |       |

          |       |-------aaiw

          |       |       |

.     |       |       |-------dodo

.     |       |       |-------duchess

.     |       |       |-------hatter

.     |       |       |-------march_hare

.     |       |       |-------queen

.     |       |       |-------tarts

.     |       |

.     |       |-------biog

...

힌트 : | - 문자를 담을, 최소 2개의 변수가 필요할 것입니다.


양식 꼭 수정하겠습니다 .ㅠ,ㅠ

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

6. Command-Line Options and Typed Variables  (0) 2011.04.20
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