본문 바로가기

BashShell Programming

4. Basic Shell Programming

|참조| Learning the bash Shell 3rd Edition(Covers bash3.0) [O'REILLY]

4. Basic Shell Programming

 

우리가 이전 장에서 제시한 사용자 정의 기법에 익숙해져 있다면.

쉘 프로그래밍은 당신의 환경에서 그 동안 원하지만 할 수 없었던 여러 가지 변경 작업들을

수행 할 수 있게 해 줄 것입니다.

bash는 고급 프로그래밍 기능을 가진 인터프리터 커맨드 타입 중 하나입니다.

비록 syntax가 대부분의 전통적인 프로그래밍 언어처럼 훌륭하거나 일관성 있지는 않으나,

강력하고, 유사한 유연성을 가지고 있다.

사실, bash는 소프트웨어 프로토 타입 작성을 위한 완벽한 환경으로 사용할 수 있습니다.

 bash 프로그래밍의 일부 측면은 사용자 정의 기술의 확장입니다.

우리는 이미 다른 비슷한 전통적인 프로그래밍 언어의 기능을 보았습니다.

만약 여러분이 프로그래머가 아니더라도 이 책을 볼 수 있으며,

이전 챕터보다 더 많은 정보를 얻을 수 있도록 구성하였습니다.

파스칼 또는 C 같은 기존의 프로그래밍 언어에 대한 경험이 있다면(꼭 필요하지는 않음)

, 후속 챕터를 이해하는데 도움이 될 것입니다.

 

Shell Scripts and Functions

 

스크립트는(쉘 명령어를 포함하는 파일) 쉘 프로그램입니다.

이전 챕터에서 언급한 .bash_profile과 다른 환경 파일이 쉘 스크립트입니다.

스크립트는 텍스트 편집기를 선택하여  만들 수 있습니다.

 

일단  만들어진 스크립트를 실행하는 방법은 두 가지의 방법이 있습니다.

1. source로부터 script name을 입력하는 것 입니다.

이것은 스크립트에 있는 명령을 읽고 실행하도록 입력할 경우에 발생합니다.

 

2. 스크립트를 실행하는 두 번째 방법은 마찬가지로 내장된 명령의 이름과

 hit RETURN을 호출하는 단순한 입력입니다.

물론 이것이 더 편리한 방법입니다.

이 방법은 스크립트를 마치 유닉스 명령처럼 보이게 만듭니다.

사실 몇 가지 "일반적인" 철자를 포함하고, 특정 시스템에서의 man과 시스템 관리자를

위한 각종 명령어들은 쉘 스크립트로 구현됩니다.

(, 원래 c나 다른 언어로 구현되지는 않았습니다)

그 결과 user command files built-in commands 사이의 구분이 모호합니다. 

이것은 유닉스 확장성의 한 요소이며, 이런 까닭에 프로그래머들은 즐겨 사용합니다.

오직 당신의 명령어 검색 경로에 스크립트가 위치할때 만 이름을 입력하여

스크립트를 실행할 수 있습니다.

혹은 (현재 디렉토리)가 명령어 검색 경로의 일부입니다,

, 스크립트의 디렉토리 경로 (쳅터 3 장에서 논의한)

이것은 당신의 경로에없는 경우, ./scriptname을 입력합니다,

이는 정말 스크립트의 절대 경로명을 입력하는 것과 똑같다.(챕터 1 장 참조)

 

이름으로 쉘 스크립트를 호출하기 전에, 당신은 또한 "실행" 권한 부여해야합니다

당신은 유닉스 파일 시스템 잘 알고 계시다면,

파일 권한의 종류는 세가지 있다는 것을 알 것이다. (읽기, 쓰기, 실행)

그리고 이러한 권한은 사용자의 세 가지 범주에 적용된다.

(파일의 소유자, 사용자 그룹, 그리고 다른 사람)

일반적으로 당신은 텍스트 편집기로 파일을 만들 때, 당신에게는 파일 읽기 및 쓰기 설정

이 되어 있으며, 다른 사람에 대해서는 오직 읽기 권한만 가능하도록 설정되어 있다.

 

따라서 당신은 명시적으로 chmod 명령을 사용하여 실행할 스크립트에 

권한을 부여 해야 합니다.

이 작업을 수행하는 가장 간단한  입력 방법 :

$ chmod +x scriptname

 

당신이 이후 스크립트를 변경하여도 텍스트 편집기의 권한은 유지됩니다.

만약 당신이 스크립트에 실행 권한을 추가하지 않고, 호출하려고 한다면,

쉘은 이런 메세지를 출력한다:

scriptname: Permission denied

 

그러나 쉘 스크립트를 실행하는 두 가지 방법에는 더 중요한 차이가 있습니다.

그들은 당신의 로그인 세션의 일부라면, source를 사용하는 동안에 스크립트가

실행되는 명령어의 원인

"단지 이름은"메소드는 쉘을 할 원인 가지의 시리즈

첫째, 그것은 subprocess로 쉘의 다른 복제본이 실행, 이것을 subshell이라고 합니다.

그 후 subshell은 스크립트에서 명령어를 얻어서, 그것들을 실행하고, 종료하고,

다시 부모 쉘에게 컨트롤을 넘겨줍니다.

 

 

 

그림 4-1은 쉘 스크립트의 실행 방법을 보여줍니다.

hattergryphon 명령어가 포함된 alice라는 간단한 쉘 스크립트가 있습니다.

a에는 당신이 수동으로 입력한 대로라면 alice에는 같은 쉘 안에서 두 개의 명령을 돌게

하는 소스가 입력되어 있습니다.

b. 당신이 방금 입력한 alice에 무슨 일이 발생할지 보여준다.

명령들은 부모shell subshell이 끝날 때까지 기다리는 동안 subshell에서 실행됩니다.

 

c. alice&(백그라운드프로세스)로 돌렸을 때 무슨 일이 발생하는지 보여주는 b의 상황과 비교되는 흥미로운 것을 발견할 수 있을 것이다.

당신이 1장에서 기억하기로는 &는 백그라운드에서 명령이 실행된다고 하였습니다.

어느 것이 진짜 Subprocess에 대한 또 다른 용어일까요?

 

C b사이에 상당한 차이가 있다고 밝혀졌습니다.

명령을 실행하는 동안에도 터미널 또는 워크스테이션에 제어권을 가집니다.

당신은 다음 지시를 내리기 위하여, 작업이 종료될 때까지 기다릴 필요가 없습니다.

 

위와 같이 subshell 사용에는 많은 파급효과가 있습니다.

중요한 사실은 우리가 마지막 장(.,TERM, EDITOR, PWD)에서 본 export 환경 변수는 subshells로 알려져 있습니다. 반면 다른 쉘변수 (:,bash_profile export없이 등록된 것)

는 그렇지 않습니다.

기타 Subshell에 관련된 다른 이슈들은 지금 설명하기에는 어렵습니다.

7장과 8장에서 subshell I/O와 각각의 프로세스의 특성을 더 자세히 다룰 것입니다.

지금은 그저 스크립트가 일반적으로 subshell에서 실행된다는 것을 명심해야 합니다.

 

Functions

bash의 함수 기능은 비슷한 시스템 속의 VBourne shell과 다른 shell들의 확장버전입니다.

함수는 일종의 스크립트 내에서의 스크립트입니다.

일부 shell code를 이름으로 정의하고 shell memory에 저장하여 나중에 호출하거나

실행하는데 사용할 수 있습니다.

 

함수는 크게 두 가지 이유로 쉘 프로그래밍을 향상시킵니다.

1. 함수를 호출할 때 그것은 이미 shell memory에 안에 있으므로 그 결과가 빠르게

실행됩니다. 현대 컴퓨터는 충분한 메모리가 보장되어서, 함수가 차지하던 공간을

걱정할 필요가 없어졌습니다. 이러한 이유로 대부분의 사람들은 일반적으로 사용되는

함수를 가능한 많이 정의하기 보다 스크립트에 보관합니다.

 

2. shell 스크립트들을 개발하고 유지하기 쉬운 모듈 덩어리로 체계화하는데

이상적입니다. 만약 당신이 프로그래머가 아니라면 함수(또한 다른 언어들 안에

프로시져들 또는 서브루틴을 호출) 없는 삶이 어떨지 물어본다면 아마도 꾸중을

들을 것입니다.

함수를 정의하기 위해선 두 가지 형태 중 하나를 사용할 수 있습니다:

function functname{
shell commands}
or:
functname ()
{
shell commands}

 

둘 사이에는 기능적인 차이가 있습니다. 우리는 위의 두 가지 형태를 사용할 것입니다.

또한 unset -f functname 을 사용하여 함수정의를 삭제할 수도 있습니다.

함수는 정의될 때, 메모리에 이름과 정의(shell 에 포함된 명령)를 저장한다고 알립니다.

declare f 입력하여 login한 세션 안에서 어떤 함수가 정의되었는지 알아낼 수 있습니다.

쉘은 알파벳순서대로 order by 함수 이름뿐만 아니라 모든 함수의 정의를 출력합니다.

이것은 긴 출력을 초래 할 수 있으므로 원하는 것을 파이프를 통해 output하거나,

텍스트 편집기로 검사하기 위하여 파일에 리다이렉션(>)을 합니다.

 

단지 함수의 이름을 보고 싶다면 당신은 declare F를 사용할 수 있습니다.

이점 외에도 함수와 스크립트 사이에는 두 가지 중요한 차이점이 있다.

 

1.      함수는 스크립트에서 사용자가 이름으로 그들을 호출할 때처럼 별도의 프로세스에서

실행되지 않는다.

함수를 실행한다는 의미는 당신이 로그인할 때 또는 소스명령으로 어떤 스크립트를

호출 할 때의 .bash_profile과 가깝다.

 

2.   스크립트 또는 실행 프로그램과 동일한 이름을 가질 경우 함수가 우선이 된다.

 

명령어의 다양한 소스에 대한 우선 순위를 알아보도록 합시다.

쉘에  명령을 입력하면:

1.   별칭들(Aliases)
2.   function, if, for 같은 키워드들. 
3.   함수들(Functions)
4.   Cd  type같은 내장기능
5.   스크립트와 실행프로그램 
      PATH 환경 변수에 나열된 디렉토리에 있는 것들

 

따라서 alias는 함수나 같은 이름의 스크립트보다 우선시 됩니다.

그래도 기본 built-ins 명령을 사용하면, 우선 순위의 순서를 변경할 수 있습니다

builtin하고 활성화하십시오.

이것은 당신이 동일한 이름을 가진 함수를, 별칭 및 스크립트 파일을 정의할 수 있습니다.

 그리고 당신이 실행하려는 하나를 선택합니다.

7 장에서 command-line 처리에 관한 섹션에서 더 자세히 이 과정을 검토합니다.

 

만약 command의 정확한 출처를 알 필요가 있다면 우리가 제 3 장에서 본

내장된 명령에 옵션이 있습니다

자체 유형 bash는 위에 나열된 검색 위치에 따라 명령을 해석하는 방법으로 출력됩니다.

하나 이상의 인자타입을 총족한다면 그것은 차례로 각 명령에 대한 정보를 출력합니다

함수와 alias가 모두 dodo라고 불리는 스크립트가 있다면 dodo를 입력한 경우

 dodo alias 일 거라고 말할 것입니다.

 

type command의 특정 세부정보를 찾을 수 있는 몇 가지 옵션이 있습니다.

dodo에 대한 모든 정의를 알아보려는 경우 type a를 사용할 수 있습니다.

이것은 다음과 유사한 결과를 보여줍니다.

$ type -all dodo
dodo is aliased to `echo "Everybody has won, and all must have prizes"'
dodo is a function
dodo ( )
{
echo "Everybody has won, and all must have prizes"
}
dodo is ./dodo

 

또한 이것은 -p 옵션을 사용하여 실행 파일이나 shell scripts가 있는 commands

대한 검색을 제한하는 것도 가능합니다.

명령을 입력하면 bash 실행파일 또는 쉘 스크립트, 파일의 pathname이 리턴 됩니다.

만약 그렇지 않으면 아무것도 출력되지 않습니다.

-P 옵션은 t의 결과로 파일이 반환되지 않더라도, 실행파일 또는 shell script

 강제적으로 찾습니다.

 

type의 기본 출력은 자세합니다:

type은 별칭 또는 함수에 대한 전체 정의를 제공합니다.

- t 옵션을 사용함으로써, 단 하나의 단어로 보여 줄 것입니다.

alias, keyword, function, builtin, or file.

For example:

$ type -t bash
file
$ type -t if
Keyword

 

- t 옵션은 또한 다른 모든 옵션과 함께 사용할 수 있습니다.

Shell Variables

 

Bash shell 변수로부터 프로그래밍적인 기능성을 끌어내었다.

우리는 이미 변수의 기초를 보았다. 간단히 요점을 돌이켜보면

일반적으로 문자열의 형태로 이름 붙여 장소에 데이터를 저장한다.

그리고 그 값들은 이름 앞에 $기호를 붙여 사용할 수 있다. 환경변수라는 특정변수는

일반적으로 대문자로 그들의 값은 서브 프로세스로 만드는 걸로 알려져 있다.

당신이 프로그래머라면 이미 모든 주요 프로그래밍 언어에 대한 몇 가지

방법으로 변수를 사용하는 것을 알고 있을 것이다.

사실 언어 사이의 차이점을 특성화하는 중요한 방법이 변수에 대한 그들의 시설을

비교하는 것이다,

Bash의 공간은 문자열에 중점을 뒀다.

( 그러므로 파스칼과 같은 범용보다 SNOBOL 같은 특수 용도의 언어와 관련이 있다. )

이것은 또한 Bourne shell C shell 에도 적용된다.

하지만 bash는 명시적으로 정수를 처리하기 위한 추가적인 메커니즘을 가지고

이들을 넘어섰다.

Positional Parameters

 

우리가 이미 보았듯이,  varname=value 형식으로 변수에 대한 값을 정의할 수 있다.

$ hatter=mad
$ echo "$hatter"
mad

 

당신이 로그인할 때 shell은 몇 가지 환경변수를 미리 정의합니다.

거기에는 shell프로그래밍에 없어서는 안될 다른 내장변수들이 있습니다.

우리는 그것들 중 몇몇을 볼 것이고, 다른 것들은 여벌로 남겨둘 것 입니다.

 

가장 중요하고, 특별한 내장 변수는 위치 매개 변수입니다.

쉘 명령어 실행되기 전에 명령 라인이 처리되고(메타 문자 처리, 인용 부호 제거등)

매개변수로 분리 됩니다. 각 매개변수는 위치 매개변수 $0,$1,$2 … $n 에 해당합니다.

위치 매개 변수 $0은 명령 라인에 입력될 때 명령명을 포함하며, $1은 첫 번째 매개변수를,

$2는 두 번째 매개변수 등 이런 식으로 의미를 갖게 됩니다.  

                 명령 라인에 지정된 현재 매계변수의 수는 변수 $#에 포함되어 있습니다. 이 변수 값은

                 지정된 매개변수의 실제 개수 입니다. , $0의 명령명은 포함하지 않습니다.

 

변수 $* 이나 $@ 모두. 모든 위치 매개변수를 참조할 수 있습니다. 이 변수들 중 하나를

사용하여, $1로 시작하는 각 매개변수 사이에 포함된 공백과 함께 모든 위치 매개변수를

포함하는 문자열을 리턴합니다.

변수 $@ $*는 이중따옴표로(“ ”) 둘러싼 경우를 제외하면 동일합니다.

Operator

Substitution

“ $*”

모든 위치 매개변수를 하나의 값으로 처리.

“$@”

각 위치 매개변수를 독립된 값으로 리턴

 

변수 $0 ~ $9, $#, $@ 모두 읽기 전용 변수이다. , 값을 할당할 수 없다.

 

예를 들어 아래와 같은 간단한 쉘 스크립트가 있다고 가정하자.

echo "alice: $@"
echo "$0: $1 $2 $3 $4"
echo "$# arguments"

 

alice 스크립트를 불러와서 alice in wonderland  라고  입력 하였을 경우, 

아래의 결과물을 볼 수 있다.

alice: in wonderland
alice: in wonderland
2 arguments

 

이 경우 $3, $4는 설정하지 않았으므로, 쉘은 빈 문자열(Null)을 돌려준다.

 

Positional parameters in functions

 

쉘 함수는 쉘 스크립트와 같은 방식으로 * #같은 특별한 위치매개함수를 쓴다

당신이 alice를 함수로 정의하기 원한다면  .bash_profile 또는 환경파일에

다음과 같이 입력하면 됩니다.

function alice
{
echo "alice: $@"
echo "$0: $1 $2 $3 $4"
echo "$# arguments"
}

 

alice in wonderland라고 입력 하였을 경우, 같은 결과를 얻을 수 있을 것 입니다.

 

일반적으로 여러 쉘 함수는 단일 쉘 스크립트 안에 정의됩니다.

그러므로 각각의 함수는 자신의 인자를 처리해야 할 필요가 있습니다.

결국 각 함수는 별도의 위치매개함수를 필요로 한다는 의미입니다.

물론 각 함수는 변수의 자체 복사본을 가지고 있다.

(비록 함수는 스크립트처럼 자신의 subshell에서 실행되지 않는다 해도)

우리는 이와 같은 변수들을 local변수라고 합니다.

 

어쨌든  함수 안에서 정의된 local이 아닌 다른 변수(그들은 global 변수이다)

그 변수의 값이 전체 쉘 스크립트 전역으로 알려지는 것을 의미한다.

예를 들어 당신은 이것을 포함하는 ascript를 호출하는 쉘스크립트를 가지고 있다고

가정하자.

function afunc
{
echo in function: $0 $1 $2
var1="in function"
echo var1: $var1
}

Nounset 옵션이 켜져 있다면 쉘은 에러 메세지를 반환할 것이다.

 

var1="outside function"
echo var1: $var1
echo $0: $1 $2
afunc funcarg1 funcarg2
echo var1: $var1
echo $0: $1 $2

 

만약  ascript arg1 arg2  으로  이 스크립트를 입력하였다면:

var1: outside function
ascript: arg1 arg2
in function: ascript funcarg1 funcarg2
var1: in function
var1: in function
ascript: arg1 arg2

 

, 함수 afunc의 변수 val1의 값은 “in function”에서 “outside function” 으로 변경됐고

변경된 값은 main script와 함수 안에서 $1$2가 다른 값을 가지는 동시에 함수의

외부에도 그 변경 값이 알려졌다.

쉘 스크립트 환경에서 함수가 실행되었고, $0은 스크립트의 이름을 가지고 있기 때문에

변경되지 않는다.

그림 4-2 그래픽으로 각 변수의 범위를 보여준다.

 

 

 

Local Variables in Functions

 

Here is the function from our last example with the variable var1 made local:

 

function afunc
{
local var1
echo in function: $0 $1 $2
var1="in function"
echo var1: $var1
}

 

Now the result of running ascript arg1 arg2 is:

 

var1: outside function
ascript: arg1 arg2
in function: ascript funcarg1 funcarg2
var1: in function
var1: outside function
ascript: arg1 arg2

 

 

 

 

 

 

 

 

Quoting with $@ and $*

 

$@”와“$*”에 대해 좀더 자세히 살펴보자.  이 변수들은 두 가지 큰 특징이 있다.

1. $의 요소는 공백 대신에 IFS의 첫 문자로 구분되는 것일까?.

output 의 유연성을 주기 위해서다. 간단히 예를 들자면 위치매개변수의

리스트를 출력하기 원할 때 콤마로 구분한다고 생각해보자.

IFS=,
echo "$*"

 

스크립트에서 IFS의 변경은 위험이 따른다. 하지만 이것은 스크립트에 따라 다르지만

아무것도 없는 동안은 아마 괜찮다

이 스크립트가 arglist를 호출했다면 arglist alice dormouse hatter

 alice,dormouse,hatter 산출했을 것이다.

5장과 10장은 IFS의 변경에 대한 다른 예제를 포함한다.

 

2.왜 “$@”는 N과 같이 큰따옴표로 구분하여 실행될까? 

당신이 그것을 별도의 값으로 사용할 수 있게 하기 위해서다.

예를 들어 스크립트 안에서 위치 매개변수와 같은 함수를 호출한다고 생각해보자

function countargs
{
echo "$# args."
}

 

스크립트가 위와 같은 인자와 함께 호출된다고 가정해보자.

그렇다면 countargs명령은 “$*”을 포함한다. 함수는 하나의 인자를 출력할 것이다.

그러나 countargs $@”명령한다면 함수는 3개의 인자를 출력할 것이다.

 

More on Variable Syntax

 

당신이 shell 변수로 할 수 있는 더 많은 것들을 보기 전에 간소화해서 만드는 것을

 언급해야 한다.

 

변수의 값을 가져오는 $varname의 구문은 실제로 좀더 일반적인 구문 ${varname}의 양식보다 간단하다.

왜 두 구문인가?

 

한가지 예를 들어 코드가 9개 이상의 위치매개변수를 참고한다면 좀더 일반적인 구문이 필요하다.

당신은 10번쨰에 대해 $10대신에 ${10}을 사용 해야 한다. 그 외  유저아이디 뒤에  밑줄을 붙이고 싶다면  다음과 같은 경우를 고려해야 한다.

echo $UID_

 

쉘은 변수의 이름으로 UID_를 사용하려 합니다.  우연이 아닌 이상 $UID_는 이미 존재한다.

이것은 아무것도 출력하지 않는다. (그 값은 널이거나 비어있는 문자열 “”이다)

원하는 결과를 얻으려면 당신의 쉘변수를 중괄호 안에 넣어야 한다.

echo ${UID}_

 

변수이름에 글자, 숫자 또는 밑줄이 아닌 문자가 붙는다면 중괄호({})를 생략하는 것이 안전하다.

 

String Operators

 

curly-bracket{중괄호} 구문은 쉘의 문자열 연산자를 허용한다.

문자열 연산자는 full-blown programs이나, 유닉스 외부 유틸리티를 사용하지 않더라도

변수 값을 다양한 방법으로 조작을 할 수 있도록 허용합니다. 

만약 당신이 아직 프로그래밍의 특징을 마스터 하지 않았더라도,

많은 문자열 연산자를 다룰 수 있음을 이후의 챕터에서 확인 할 수 있을 것입니다.

 

특히 문자열 연산자는 다음을 수행합니다.

- 변수들의 존재를 보장함 (즉 정의가 되었으며, null 값이 아닌)

- 변수에 대한 기본 설정 값

- 설정이 되지 않은 변수 값으로 인한 결과에 대한 에러를 잡아낸다.

- 동일한 패턴의 변수 값을 제거한다.

 

Syntax of String Operators

 

문자열 연산자의 구문 뒤에 기본 개념은 변수의 이름뿐 아니라 올바른 중괄호가 삽입되어진 표시가 있는 특수 문자이다.

필요할 수도 있는 연산자의 모든 인수들은 올바른 연산자에 의해 삽입되었다.

문자열 처리 연산자의 첫 번째 그룹은 변수의 존재와 확실한 조건 아래의 기본 값 대체를 위한 테스트를 한다.

 

Operator

Substitution

${varname:-word}

varname이 존재하며, 널이 아닐 경우에 그 값을 리턴한다; 그렇지 않으면 word를 반환한다.

목적 : 만약 변수가 정의 되지 않았다면 기본값을 반환한다.

:  만약 count가 정의 되지 않았다면 ${count:-0}  0부터 평가합니다.

${varname:=word}

varname이 존재하며, 널이 아닐 경우에 그 값을 리턴한다; 그렇지 않으면 word로 설정한 후, 그 값을 반환합니다.

위치상으로와 특수 매개 변수는 이 방법을 할당 할 수 없다.

목적 : 만약 기본값이 정의 되지 않았다면,변수를 설정한다.

:  만약 count가 정의 되지 않았다면, 

${count:=0}  0으로 설정한다.

${varname:?message}

varname이 존재하며, 널이 아닐 경우에 그 값을 리턴한다; 그렇지 않으면 varname을 출력:

message를 따르며, 현재 명령이나 스크립트

(오직 비-대화형 쉘들)를 중지한다.

기본 message 매계 변수가 없거나, 설정이 안되어 있는 message의 생산을 생략한다. 

목적 : 목적 : 정의되지 않는 인수들의 결과에서 나오는 에러 캐치

: count: undefined!”와 만약 정의 되지 않은 count가 있다면 {count:?undefined!}를 출력한다.

${varname:+word}

만약 varname이 존재하며, null이 아닐 경우 word 반환합니다; 그렇지 않으면 null을 반환합니다.

목적 : 변수의 존재에 대한 테스트

: 만약 count가 정의 되어 있다면 ${count:+1} 1을 반환합니다.( 각 의미가 true일때)

${varname:offset:length}

수행 문자열을 확장한다.

이것은 offset으로 시작되는 $varname 문자열과

 문자 최대 길이를 반환합니다.

첫번째 문자는 $varname 0에 위치합니다.

만약 길이가 생략 된다면, 문자열은 offset $varname의 끝으로 계속됩니다.

-offset 0 미만이라면,  $varname의 끝으로 부터

위치가 잡혀 있다.

 -varname @라면, 길이는 매개 변수 offset에서 위치 매개 변수시작점은 숫자입니다.

목적: 문자열의 부분 반환(하위 문자열이나 조각)

: count frogfootman으로 설정됐다면, 

${count:4} footman를 반환한다.

      ${count:4:4} foot을 반환한다.

 

문자열 확장 연산자는 bash 2.0 이전 버전에서는 사용할 수 없습니다.

 

 

먼저 이러한 연산자는 사용자가 이것들을 생략하는 경우, 인수 입력라인을 위한

기본적인 설정을 하기에 이상적입니다.

 

Task 4-1

당신은 많은 앨범 컬렉션을 가지고, 소프트웨이에 그것들을 적어서,트랙을

보관하기 원합니다. 보유중인 아티스트들의 앨범리스트에 대한 파일이 있다고

가정하에 다음과 같은 라인을 코딩 할 수 있다

 

5 Depeche Mode

2 Split Enz

3 Simple Minds

1 Vivaldi, Antonio

최고라인에 N 이라고 표현되도록 프로그램에 적는다.

, 당신이 가진 대부분의 앨범의 아티스트들이 누군지 모릅니다.

N에 대한 기본값은 10이어야 합니다. 프로그램은 입력 파일의 이름을

위해서 하나의 인수를 가져야 하며, 많은 코드를 출력하기 위해서

선택적으로 두 번째 인수를 가져갑니다.

 

이런 타입의 스크립트에 대한 가장 최고의 접근은 I/O 리다이렉트와 파이프를 이용하여 내장 유닉스 유틸리티와 조합하는 것입니다.

이런 방식을 유닉스의 "building-block" 방식이라고 하며 프로그래머 사이에서

인기가 좋습니다.

이 기술은 우리로 하여금 아래와 같은 한 줄짜리 스크립트 코딩을 할 수 있게 해준다.

 

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

 

위 작업은 이렇게 이루어진다:  정렬 프로그램이 파일 안에 데이터를 정렬하며

누군가 첫 번째 인수($1)의 이름을 얻는다.

- n 옵션은 숫자로 각 라인의 첫 번째 단어를 해석하도록 지시한다. (문자열 대신)

- r 옵션은 반대로 비교하므로, 내림차순으로의 정열을 지시한다.

 

반대되는 -N이 첫째 N 라인들의 입력을 표준 출력을 나타낼 때, Sort 의 출력은 헤드

유틸리티에 넣어집니다.

만약 2번째 인수가 주어지거나, -10이라면 -${2:-10}가 대쉬(-) 다음에 붙는

두 번째 인수를 평가하게 됩니다;

그렇지 않은 경우에는 2번째 매개 변수에 표현돼 있는 변수를 확인해보십시오.

 

우리가 쓰고 싶은 스크립트를 highest 라고 생각하세요. 그리고 그때 사용자가 highest

myfile을 쓸 경우, 그 라인은 이렇게 실행됩니다:

sort -nr myfile | head -10

 

또는 만약 사용자가 highest myfile 22 를 쓸 시에는 이렇게 실행됩니다:

sort -nr myfile | head -22

 

:- 문자열 실행자가 어떻게 기본 값을 주는지 확실하게 이해하도록 합시다.

 

이것은 실행 가능한 완벽하게 좋은 스크립트입니다. 하지만 몇 가지 문제점이 있습니다.

첫 번째로, 이것의 줄 하나가 조금 불확실합니다.

아주 적은 스크립트에선 별로 문제 될게 아니지만,

긴 스크립트를 이렇게 적을 경우엔 별로 현명하지 못한 판단이라 생각합니다.

작은 변화가 코드를 더 읽기 쉽고 편하게 할 것입니다.

 

첫 번째로, 우리는 코드에 조금 코멘트를 더 집어 넣을 수 있습니다;

#와 마지막 사이에 있는 것들은 아무거나 다 코멘트라고 합니다.

최소한으로, 스크립트는 그 스크립트가 어떤 것을 실행하고 어떤 인수를 받아드리는지를

나타내는 적은 양의 코멘트 라인들로 시작해야 합니다.

 

두 번째로, 우리는 변수명을, 위치매개변수에 보통 변수와 함께 연상 기호명

값을 정해줌으로써 더 향상 시킬 수 있습니다.

마지막으로, 우리는 빈 줄을 집어넣어, 여러 가지를 조금 더 멀리 떨어지게 할 수 있습니다.

빈 줄은, 주석들처럼, 무시됩니다. 여기 조금 더 읽을 수 있는 버전이 있습니다.

 
#
#      highest filename [howmany]
#
#      Print howmany highest-numbered lines in file filename.
#      The input file is assumed to have lines that start with
#      numbers. Default for howmany is 10.
#
filename=$1
howmany=${2:-10}
 
sort -nr $filename | head -$howmany

 

주석들 안에 howmany의 주변에 있는 대괄호들은 유닉스 documentation안에

convention에 붙여줍니다,

대괄호 안은 선택적인 인수라는 것을 나타냅니다.

 

우리들이 만든 변화들이 코드를 더욱 읽기 편하게 만들었지만, 이게 더 잘

실행되거나 하지는 않습니다.

만약 사용자가 스크립트를 인수 없이 만든다면?

만약 값들을 정의해주지 않는다면, 위치 매계변수들은 null 값이 기본으로 되는 것을

떠올려 보십시오.

만약 인수들이 없다면, $1 $2 null을 가지게 됩니다.

변수 howmany ($2)는 기본값을 10으로 정하게 되지만,

filename ($1) 에는 기본값이 없습니다.

그 결과 이 명령어가 실행됩니다:

 

sort -nr | head -10

 

공교롭게도, 만약 정렬이 파일 이름 인수 없이 호출되었다면, 파이프나 사용자의

터미널로부터의 입력을 기대합니다.

이것은 파이프가 없었기 떄문에 터미널을 기대 할 것입니다.

이 의미는 곧 스크립트가 멈춘 것처럼 보여집니다!

비록 당신이 스크립트를 나가서 계속 CTRL-D 혹은 CTRL-C를 쳐도,

순진한 사용자는 이것을 모를 것입니다.

따라서 사용자에게 제공되는 하나 이상의 인수는 이러한 점을 확인하는데 필요합니다.

몇 가지 방법이 있습니다; 그 중 하나가 다른 문자열 연산자를 포함하는 것입니다.

 

우리는 라인을 이렇게 대체합니다 :

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

 

원인은 두 가지입니다.

사용자가 아무런 인수 없이 스크립트를 호출하는 경우 :

먼저 쉘이 다소 부정확한 message를 보일 것 입니다 :

 

highest: 1: filename missing.

 

표준 에러 출력. 둘째, 스크립트 나머지 코드를 실행하지 않고 종료됩니다.

다소 "사용하기 불편한" 조작과 함께, 우리가 약간 더 나은 오류 메시지를 얻을 수 있습니다

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

 

결과 메세지:

highest: filename: missing.

(왜 이렇게 되는지 당신은 이해합니다.) 물론, 무엇이든 원하는 메세지가 보여지도록

하는 방법을 우리는 5장에서 알게 될 것입니다.

 

다음으로 넘어가기 전에, 나머지 세 개의 연산자를 면밀하게 더 봅시다. 

Table 4-1과 어떻게 작업 솔루션 안에 반영 할 수 있는지에 대해서.

The := 연산자는 거의 똑같이 한다.

as :-, 변수가 존재하지 않는 경우, 주어진 단어에 변수 값을 설정  "부작용"을 제외하고

그러므로 이같이 사용한다 := 우리의 스크립트 안의  :-, 그러나 우리는 할 수 없다

그러나 만약 우리가 바뀔 경우: 위치 매개 변수의 값을 정하도록 시도하는 것은

허용되지 않습니다.

그러나 만약 교체한다면 :

howmany=${2:-10}
with just:
     howmany=$2

 

그리고 실제 명령어 라인은 (우리가 시작했던 것처럼) 밑으로 이동합니다,

그러면 우리는 : = 연산자 가 필요할 것이다:

 

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

 

연산자 : + 값이 대체된다. 만약 주어진 변수가 존재하고 null이 아니라면.

여기 예제에 어떻게 사용할 수 있는지에 대한 방법입니다 :

우리는 유저에게 스크립트의 출력으로 헤더 라인을 추가 선택할 수 있게 해주기를

원합니다.

만약  -h 옵션을 타이핑하면 라인 앞에 출력 될 것입니다.

ALBUMS   ARTIST

 

추가 가정하면 이 옵션은 변수 헤더에 위치하게 됩니다. , $header -h 입니다.

만약 옵션이 설정되거나 null이 아닐 경우 (어떻게 다른 위치 매개변수를 방해하지 않고,

이 작업을 수행하는가에 대해서 나중에 볼 것입니다.)

 

다음과 같은 표현식은 null 입니다. 만약 변수 헤더 Null이거나,

ALBUMSARTIST\n 이것이 null이 아니라면:

${header:+"ALBUMSARTIST\n"}

 

이 뜻은 라인에 입력 할 수 있다는 것이다 :

    echo -e -n ${header:+"ALBUMSARTIST\n"}

 

command line 전의 오른쪽에 실제 작동하는 -n 옵션은 echo를 발생한다.

인수를 출력한 후에는 LINEFEED를 출력하지 않는다.

따라서 echo의 정책은 아무 것도 출력하지 않는 것인가? 빈 줄 조차도?

만약 header null이라면;

그렇지 않으면 그것은 헤더 라인과 LINEFEED를 출력합니다(\n)

-e 옵션은 echo를 해석하게 만듭니다.

\n 은 문자 그대로보다 오히려 LINEFEED

우리는 관심 있는 문자열의 일부를 "골라" 사용할 수 있습니다.

우리의 스크립트 라인을 지정할 수 있다고 가정합니다. 

정열된 리스트, 한번에 하나, 변수 album_line.

우리는 하위 문자열을 확장 하여 그냥 엘범 이름과 무시된 앨범의 수가

출력 되길 원합니다:

문자 표현식::

  echo ${album_line:8}

이 문자 위치 8로 부터 전부 출력됩니다, 특히 이것은 이후 각 앨범 이름의 시작입니다.

 

만약 숫자를 출력하기 원하고, 앨범 이름이 아니라면,

우리는 문자열의 길이를 제공하여 이렇게 할 수 있습니다:

echo ${album_line:0:7}

비록 이 예제는 오히려 쓸모 없어 보일 수 있지만,

이것은 당신에게 어떻게 하위 문자열을 사용할지에 대해 느끼게 해줄 것입니다.

하위 문자열은 매우 유용하게 사용 할 수 있으므로, 프로그래밍 기능 일부와의 결합에

대해서는 후에 책에서 언급할 것입니다.

 

Patterns and Pattern Matching

우리는 이후 챕터에 4-1의 작업을 통해 계속 솔루션을 수정해 나갈 것입니다.

다음 문자열 연산자의 타입은 변수의 문자열 값의 패턴들의 부분들을 비교하여 사용합니다.

패턴, 우리는 문자열은 와일드 카드 문자를 포함 할 수 있음을 챕터 1에서 보았습니다.

(*, ?, 그리고 케릭터셋들과 범위를 위한 []).

 

4-2  배쉬의 패턴 매칭 연산자 테이블 목록

Operator

Meaning

${variable#pattern}

variable 값의 전반부에서,

패턴과 일치하는 가장 작은 부분을 찾아 제거

${variable##pattern}

variable 값의 전반부에서,

패턴과 일치하는 가장 큰 부분을 찾아 제거

${variable%pattern}

variable 값의 후반부에서,

패턴과 일치하는 가장 작은 부분을 찾아 제거

${varname:+word}

variable 값의 후반부에서,

패턴과 일치하는 가장 큰 부분을 찾아 제거

${variable/pattern/string}

${variable//pattern/string}

variable 패턴이 가장 큰 부분은 문자열로 교체된다.

첫 형식은 오직 첫번째로 일치되는 부분만 교체한다.

두번째 형식은  모든 일치되는 부분을 교체한다.

만약 패턴이 a # 으로 시작되면,

variable 전반부에서 match 될 것이다.

만약 패턴이 a%로 시작된다면,

variable 후반부에서 match 될 것이다.

만약 문자열이 null이라면 match는 삭제된다.

만약 variable is@or * 이라면,

이 연산자는  각 위치 매개 변수에 차례로 적용된다.

그리고 결과 목록은 확장된다.

 

* 패턴 매칭 및 교체 연산자는 bash 2.0 이전 버전에서 사용할 수 없습니다.

 

이들은 기억하기 힘들 수 있습니다; 여기에 편리한 기억을 돕는 장치는 :

숫자 기호는 숫자에 앞서기 때문에 #가 앞쪽과 일치;

퍼센트 기호는 숫자를 따르기 때문에 %가 뒤쪽과 일치.

 

패턴-매칭 연산자의 전통적인 사용은 이를 테면 디렉토리 접두사와 파일이름 접미사와

같은 경로명의 구성을 제거합니다.

즉 이것은 연산자의 모든 작동 방법을 보여줍니다.

변수 경로가  /home/cam/book/long.file.name 값을 가지고 있다고 가정한다면:

 

Expression

Result

${path##/*/}

long.file.name

${path#/*/}

cam/book/long.file.name

$path

/home/cam/book/long.file.name

${path%.*}

/home/cam/book/long.file

${path%%.*}

/home/cam/book/long

 

여기에 사용되어진 두 가지의 패턴들은 /*/입니다, 두 개의 슬레쉬뿐 아니라 .*  

각 매치 됩니다, 각 점을 따르는 모든 것들이 매치됩니다.

크고 작은 패턴 매칭 연산자는 비슷한 출력을 제외하고 * 와일드 카드 연산자와 함께

사용됩니다.

예를 들어, 만약 파일명이 alicece라는 값을 가지고 있다면,

${filename%ce} ${filename%%ce}alice 라는 결과를 생산할 것입니다.

왜냐하면 이것은 정확하게 ce가 일치되기 때문입니다;

매칭되는 문자열 ce$filename 뒤에 표시됩니다.

크고 작은 매치 둘 다 ce로 끝나는 그룹들을 매칭하며 그것을 지웁니다.

그러나,  * 와일드 카드를 사용했다면, ${filename%ce*}alice를 생성합니다.

왜냐하면 ce 뒷 글자에 오는 다른 모든 것들도 매칭시키기 때문입니다.

${filename%%ce*}ali를 반환 할 것입니다.

 이것은 ce를 따르는 다른 모든 것들의 뒷 문장과 매칭되기 때문입니다.

첫 번째와 두 번째가 ce인 케이스입니다.

 

다음 작업은 패턴 일치 연산자 중 하나를 통합할 것입니다.

Task 4-2

당신은 웹 페이지 생성에 사용하기 위한 그래픽 파일 변환유틸리티를

작성중입니다.

당신은 PCX file과 웹 페이지에서 JPEG 파일을 변환하여 사용할 수 있기를

원하게 될 것입니다.

PCX Microsoft Windows에서 인기 있는 그래픽 파일 형식입니다.

JPEG(Joint Photographic Expert Group)는 인터넷에서 공통적인 그래픽 형식이며,

웹 페이지 확장에 매우 유용합니다.

 

사실 그래픽 파일 변환 유틸리티는 다른 수 많은 그래픽 형식들과 파일 타입들로

인해서 공통적입니다.

그들은 당신이 지정한 입력 파일을 보통 다른 범위의 포멧과 다른 포멧의

출력 파일로의 변환을 허용합니다. 이러한 경우 우리는 PCX file을 원한다.

웹 브라우저와 함께 출력 할 수 없을 때나, JPEG로 변환은 거의 모든 브라우저에서 표시할

수 있습니다. 이 프로세스의 일부가 PCX 파일의 파일 이름을 얻습니다.

출력파일을 위해서 마지막에 .jpg로 바뀐다.

outfile=${filename%.pcx}.jpg

 

쉘은 마지막 문자의 .pcx 를 찾아서 파일 이름을 획득합니다.

만약 찾았다면 .pcx는 해제되고, 다른 문자열을 반환합니다.

예를 들어서 파일명의 값이 alice.pcx라면,

표현식 ${filename%.pcx   alice를 반환 할 것이다.

.jpg는 원하던  alice.jpg  폼이 추가 되며, 그 후에 변수 outfile에 저장됩니다.

 

만약 파일명이 alice.xpm로서 부적절한 값(.pcx  없이)을 가지고 있다면

위의 표현은 alice.xpm.jpg로 평가될 것이다.

저렇게 일치 되지 않은 후 부터, 아무것도 파일명의 값을 삭제 할 수 없다.

어찌되든 .jpg는 추가됩니다.

참고, 그러나 만약 파일명에 하나 이상의 . 가 포함된다면

(예 파일명이 alice.1.pcx면 표현식은 아직 alice.1.jpg가 원하는 값을 생산 할 것이다.)

 

 

다음은 최상위 패턴 매칭 연산자의 업무 사용입니다.

Task 4-3

결과 출력을 위한 준비를 위해 text file에 필터를 실행하고 있다.

당신은 배너페이지에 디렉토리 접두사 없이 파일의 이름을 넣고 싶어한다.

스크립트가 있다는 가정에서, 파일의 경로명을 출력하여 pathname

변수에 저장한다.

 

정확히 목표는 경로에서 디렉토리 접두사를 제거하는 것입니다

다음 라인에서 그렇게 될 것입니다:

bannername=${pathname##*/}

 

이 솔루션은 이전에 표시된 예제에서 첫 번째 라인과 비슷합니다.

만약 pathname에 파일명이 있다면 */ (슬래쉬 뒤에 아무것도 없는) 패턴은 매치

되지 않으며, 표현식의 값은 pathname을 손대지 않는다.

만약 pathname book/wonderland 같은 것이 있었다면, 접두사 book/ 은 패턴과

일치되며, 표현식의 값은 wonderland를 떠나 삭제될 것이다.

만약 pathname /home/cam/ book/wonderland이라도 같은 일이 발생될 것이다:

## 이후에 가장 긴 매칭을 삭제한다, 그것은 /home/cam/book/ 전체를 삭제한다.

 

만약 #*/ 대신 ##*/을사용한 경우

표현식 home/cam/book/wonderland은 잘못된 값을 가지게 된다.

왜냐하면 "슬래쉬 뒤에 아무것도 없는" 가장 짧은 인스턴스는

문자열의 시작 부분에 그냥 슬래시입니다(/)

 

 

테이블의 마지막 연산자는 패턴 일치 및 대체를 수행합니다.

Task 4-4는 유용하게 사용 할 수 있는 단순한 작업이다.

Task 4-4

PATH에 디렉토리는 하나의 라인 밖으로 콜론 구분 기호와 출력할 때

구별하기 어려울 수 있습니다.

당신은 한 라인에, 간단한 방법으로 그들을 표시하고 싶습니다.

 

 

 

디렉토리 이름은 콜론으로 구분되므로, 가장 쉬운 방법은 각 콜론을 LINEFEED

함께 변경하는 것 입니다:

$ echo -e ${PATH//:/'\n'}
/home/cam/bin
/usr/local/bin
/bin
/usr/bin
/usr/X11R6/bin

 

각 콜론의 교체가 발생할 때마다 \n으로 교체됩니다. 우리가 앞에서 본 것처럼

-e 옵션은 echoLINEFEED \n 해석을 허용한다.

이 경우에 우리는 두 번째 두 대체 양식을 사용한다. 만약 우리가 첫 번째 폼을 사용한다면,

오직 첫 번째 콜론이 \n과 함께 교체되었을 것이다.

 

Length Operator

 

 ${#varname}하나 남은 변수의 연산자입니다.

이것은 문자열로 변수값의 길이를 반환합니다.

(6장에서 우리는 유사한 실수와 같은 값들을 어떻게 다루고, 산술 표현식에서

이들을 어떻게 사용할지를 살펴 볼 것입니다.)

 만약 filename 값이 alice.c라면, ${#filename}7을 값으로 가질 것이다.

 

Extended Pattern Matching

 

Bash  패턴 매칭 연산자의 추가 세트를 제공합니다.

shopt 옵션 extglob이 켜져있는 경우

각 연산자는 하나 이상의 패턴을 가지고 있습니다.

일반적으로 문자열은 수직 막대로 구분 됩니다.(|).

4-3에 주어진 확장 패턴 매칭 연산자를 봅시다.

   

4-3 확장 매칭 연산자

Operator

Meaning

*(patternlist)

주어진 패턴과 일치하지 않거나, 더 발생

+(patternlist)

주어진 패턴과 한 개 일치 혹은 그 이상 발생

?(patternlist)

주어진 패턴과 일치하지 않거나, 한 개 발생

@(patternlist)

주어진 패턴과 정확하게 한 개 일치

!(patternlist)

주어진 패턴과 한 개를 제외하게 너머지 모두

다를  경우

 

 

이들 일부 예는 다음과 같습니다

 

*(alice|hatter|hare)

일치하지 않거나 alice, hatter,hare 의 더 일치 횟수

그래서 null 문자열, alice, alicehatter, etc 등으로 일치될 것이다.

+(alice|hatter|hare)

null 문자열이 아닌 것을 제외한다.

?(alice|hatter|hare)

오직 null 문자열, alice, hatter, 혹은 hare으로 일치 될 것이다.

@(alice|hatter|hare)

오직 alice, hatter, 혹은 hare으로 일치 될 것이다.

!(alice|hatter|hare)

alice, hatter, hare 제외한 모든것으로 일치 된다.

 

그 값은 쉘의 와일드 카드 역시 제공 할 수 있습니다.

예를 들면 +([0-9]) 하나 이상의 자릿수의 번호와 일치

패턴도 중첩될 수 있습니다. 그래서 vt로 시작되는 rm !(vt+([0-9]))

숫자 뒤를 제외한 모든 파일을 제거 할 수 있습니다.

 

 

Command Substitution

 

토론으로부터 지금까지 우리는 변수에 값을 얻는 두 가지 방법을 봤습니다 :

할당 문에 의해, 커맨드 라인 인수로 그들을 제공하는 사용자(위치 매개 변수)

다른 방법이 있다 : 명령 치환

만역 변수의 값이 존재한다면, 이것은 명령으로의 표준 출력을 사용하실 수 있습니다

당신은 곧 이 기능이 얼마나 강력 볼 수 있습니다.

"치환 명령의 구문은" :

   $(UNIX command)

 

괄호 안에 명령이 실행됩니다.

어떠한 명령이 표현식의 값으로 표준 출력은 반환된다고 씁니다.

이 구조는 중첩 될 수 있다.

, 유닉스 명령은 치환 명령을 포함할 수 있습니다.

 

다음은 몇 가지 간단한 예입니다 :

$(pwd)의 값은 현재 디렉토리(환경 변수 $PWD 같은)

$(ls $HOME) 값은 홈 디렉토리의 모든 파일들의 이름 값

$(ls $(pwd))의 값은 모든 파일의 현재 디렉토리

$(< alice)의 값은 alice를 뒤따르는 삭제된 새로운 라인들을 포함한다.

만약 당신이 파일에 상주한 모르는 명령어의 상세한 정보를 찾으려면,

   ls -l $(입력 -path -all command-name) 입력한다.

-all 옵션은 강제로 pathname look-up-path를 입력한다.

이것이 키워드와 빌트인 등을 무시하는 원인이 된다.

만약 ch로 시작되는 모든 챕터 파일에 "명령 치환" 구문을 가지고 있다고 가정할 때,

당신이 배쉬에 대한 모든 챕터를 편집(vi)하길 원한다면 이렇게 입력할 것이다.

 

vi $(grep -l 'command substitution' ch*)

grep - l 옵션은 오직 일치하는 파일들의 이름을 출력한다.

 

명령 치환, 변수와 물결표 확장은 더블 쿼테이션(" ")에서 이루어진다.

따라서 챕터1 3의 변수가 포함된 문자들의 사용을 위한 싱글 쿼테이션의 롤은 

지금 이렇게 확장 될 것입니다:

" 싱글 쿼테이션 사용이 의심 될 때, 문자열에 포함된 변수가 아니거나 명령 치환인

경우에는 더블 쿼테이션을 사용합니다."

 

명령 치환은 Task 4-1의 앨범 데이터베이스에 관한 다음 프로그래밍 문제에

대한 해결책으로 우리를 도와줍니다

 

 

Task 4-5

4-1에 사용되는 파일은 실제로 앨범에 대한 데이터의 큰 테이블에서

파생된 리포트입니다.

이 테이블은 사용자에 의해 참조되는 “artist,” “title,” “year,”등과 같은

이름의 컬럼들이나 필드들로 구성되어 있다.

열은 새로 막대로 구분됩니다. ( |, 유닉스 파이프 문자와 같음).

테이블의 개별 컬럼을 처리하기 위해, 필드 이름은 필드 번호로

변환이 필요합니다.

FUNTION getfield를 호출한다고 가정합시다.

인자로 필드 이름이 걸립니다. 그리고 표준 출력에 해당 필드 (또는 열)

번호를 기록합니다.

루틴을 사용하여 데이터 테이블에서 컬럼을 추출하는데 도움을 받습니다.

 

 cut 유틸리티는 이 문제에 대한 자연적인 현상입니다. cat은 데이터 필터입니다:

이것은 테이블 형식의 데이터로부터 컬럼을 추출합니다.

만약 니가 원하는 입력으로의 추출을, 컬럼들의 숫자만큼 제공한다면, 

cut은 오직 표준 입력에 대한 이러한 컬럼들을 출력할 것이다.

컬럼은 문자에 위치 할 수 있거나 - 이 예제의 관한 - TAB 문자 또는 다른 기호로 구분되는

필드입니다.

우리 문제의 데이터 테이블 파일이 앨범이라고 가정한다면, 그것은 이렇게 보입니다:

Depeche Mode|Speak and Spell|Mute Records|1981
Depeche Mode|Some Great Reward|Mute Records|1984
Depeche Mode|101|Mute Records|1989
Depeche Mode|Violator|Mute Records|1990
Depeche Mode|Songs of Faith and Devotion|Mute Records|1993
...

 

여기 우리가 네 번째(년도) 컬럼을 추출하여 어떻게 잘라 사용하는지 나옵니다 :

 cut -f4 -d\| albums

 

-d 인수는 필드 구분 기호로 사용하는 문자를 지정하는 데 사용됩니다.(TAB이 기본값이다)

수직 막대는 백슬래시를 피해야 합니다.

그래서 쉘이 파이프의 해석을 시도 하지 않도록 합니다.

 

이 코드 행들과 getfield 루틴에서, 우리는 쉽게 문제에 대한 해결책을 도출할 수 있습니다.

getfield에 첫번째 인수는 사용자가 extrac하려는 필드의 이름이라고 가정합시다.

이 후 해결책은 :

fieldname=$1
cut -f$(getfield $fieldname) -d\| albums

년도 인수와 이 스크립트를 호출하면 다음과 같이 출력된다 :

1981
1984
1989
1990
1993
...

Task 4-6 cut을 사용하는데 다른 작은 문제를 보여줍니다.

Task 4-6

현재 로그인한 모든 사용자에게 메일 메시지를 보내기

이 커맨드는 누가 로그인 했는지 알려줍니다.

( 뿐만 아니라 터미널이 현재 접속중인지, 언제 접속 했었는지도)

출력은 다음과 같습니다

root        tty1   Oct 13 12:05
michael     tty5   Oct 13 12:58
cam         tty23  Oct 13 11:51
kilrath     tty25  Oct 13 11:58

 

필드는 space로 구분됩니다, TABs은 안됩니다. 우리는 첫 번째 필드가 필요하기 때문에.

 cut 명령에서 필드 구분 기호로 space를 사용하여 빠져나갈 수 있습니다.

(그렇지 않으면 필드 대신 문자 열을 사용하여 자르는 옵셥을 사용했을 것이다.)

커맨드 라인에서 인수로 공백 문자를 제공하기 위해, quotes로 묶어 사용할 수 있다.

 

    $ who | cut -d' ' -f1

 

위와 같이 누군가 출력했다면, 이 명령어의 출력은 다음과 같이 합니다 :

root
      michael
      cam
      kilrath

 

이것은 문제에 대한 해결책으로 바로 연결됩니다. 일반 입력:

    $ mail $(who | cut -d' ' -f1)

 

명령어 mail root  michael  cam  kilrath가 실행 될 것입니다.

그리고 이후에 메시지를 입력 할 수 있습니다.

 

Task 4-7은 다른 문제입니다.

파이프라인이 명령어 치환에 얼마나 유용하게 사용되는지 볼 수 있습니다.

 

Task 4-7

ls  명령은 당신에게 패턴 매칭 기능을 와일드 카드와 함께 제공합니다.

하지만 당신이 수정 날짜별로 파일을 선택하는 것을 허용하지 않습니다.

 

여기 인수를 제공한 마지막 수정 날자의 모든 파일 목록을 허용하는 함수가 있습니다.

다시 한번, 우리는 속도상의 이유로 함수를 선택합니다.

함수의 이름으로 웃기려는 의도가 아닙니다.

 

function lsd
{
date=$1
ls -l | grep -i "^.\{42\}$date" | cut -c55-
}

 

이 함수는 ls -l 명령어의 컬럼 레이아웃에 따라 달라집니다.

특히, 컬럼 42 시작 날짜와 칼럼 55에 시작되는 파일 이름에 따라 달라집니다.

이것이 유닉스의 버전에 있는 경우가 아니라면, 컬럼 번호를 조정해야 합니다.

(예를 들면, sunOS 4.1 ls -l은 컬럼 33으로 시작되는 date와 컬럼46으로

 시작하는 파일이름들을 가진다.)

 

우리는 인자로 주어진 날짜가 일치하도록 grep 검색 유틸리티를 사용하여

 ls -l을 출력합니다.

(Mon DD, e.g., Jan 15 이거나 Oct 6, 마지막에 두 공백들로 이루어진 형식으로)

이것은 오로지 날짜 인수와 일치하는 파일만의 긴 목록을 제공합니다.

grep- i 옵션이 월 이름의 모든 소문자 사용을 허용합니다.

오히려 고급 인수가 의미하는 동안, 함수 인수 뒤의 41 문자에 포함된 모든 일치되는 라인

 

grep -i 옵션은 니가 month명에 사용하는 모든 소문자를 허용한다.

오히려 멋진 인수가 의미하는 동안,

일치하는 모든 라인은  41 문자들은  함수 인자를 포함한다.”

예를 들어 lsd 'jan 15' 입력 중입니다. 

grep은 라인을 검색하기 때문에 아무 일치하는 41 문자들은 jan 15

(혹은 Jan 15)을 따릅니다.

(유닉스(시스템 V 확장명 없는) 중 일부 오래된 BSD-derived 버젼은 \{N\}

옵션을 지원하지 않습니다.

예를 들어 행에서 42에 마침표를 . \{42\}. 대신에 쓰면 됩니다.)

 

 

grep의 출력은 파이프를 통하여 오직 파일명을 검색하여 우리의 어느 곳에 있는

친구들이던(ubiquitous friend) cut 합니다.

cut의 인수는 라인의 마지막 칼럼 55를 통해 문자를 추출하도록 지시합니다.

 

명령 치환과, 파일명에 인수를 허용하는 모든 명령어들로 함수를 사용할 수 있습니다.

예를들어, 오늘 마지막으로 수정한 현재 디렉토리에 모든 파일들을 출력하려면,

오늘이 January 15th 이라고 가정하고,  이렇게 입력할 것입니다.

 

   $ lp $(lsd 'jan 15')                                         

 

lsd의 출력은 여러 라인에 있다.( 각 파일명에 한 개)

그러나 LINEFEEDslp 명령어를 위한 규칙적인 필드 구분자입니다.

환경 변수 IFS에 기본값으로 LINEFEED가 포함되어 있기 때문입니다.

 

 

Advanced Examples: pushd and popd

 

이미 bash built-in2개의 함수를 이번 챕터에 포함 시킬 것이다,

그러나 이번 챕터에서 유용한 일부 개념들을 입증할 것이다.

(당신의 bash 카피본에는 빌트인이 빠지고 구성이 된 것이라 pushd popd 함수가

 없을 수도 있다

 

Task 4-8

pushd popd 함수는 다른 디렉토리에 일시적으로 움직일 수 있도록

디렉토리의 stack을 구현하며, 쉘은 당신이 어디 있었는지 기억한다.

쉘의 기능으로 이들은 구현된다.

 

우리는 pushd popd의 중요한 기능들의 구현을 시작할 것이며,

 챕터 6에서 마무리 지을 것이다.

 

stack은 식당의 용수철 접시 용기로 생각하시면 됩니다.

당신은 용기에 요리를 넣으면, 스프링 압축되어, 대략 동일한 레벨은 맨 위에 남습니다.

누군가 음식을 원할때 접시는 stack의 가장 최근의 장소로 먼저 이동 될 것이다;

따라서 stack은 “last-in, first-out”혹은 LIFO 구조를 알고 있다.

stack의 무엇인가를 넣는 것은, 컴퓨터 과학어로 pushing 라고 합니다,

그리고 무엇인가를 맨 위에서 빼는 것을 popping이라고 부릅니다.

 

stack은 디렉토리를 기억하는데 매우 편리합니다,

그것은 시간의 임의 숫자까지 "자리를 잡고" 있을 수 있습니다.

cd 명령어의 cd - 형식이 이것을 수행할 수 있지만, 오로지 1레벨이다.

예를 들어 : 만약 당신이 첫 번째 dir 에서 두 번째 dir 로 변경하려면,

그리고 세 번째 dir로 이동한다면,

            당신은 cd를 사용할 수 있습니다 - 단지 두 번째 dir로 되돌리 수 있습니다.

만약 cd를 입력하면 - 다시, 세 번째 dir로 돌아간다,

왜냐하면 그것은 이전의 디렉토리입니다.

만약 기억 "중첩"를 원한다면 - 그리고 - 첫 번째 dir로 될아가도록 기능을 변경한다면,

당신은 pushd popd 명령과 함께 디렉토리 stack이 필요합니다.

 

여기 어떻게 그 작업을 하는지 있다:

처음에는 pushd dir 호출한다, pushd stack에 현재 디렉토리를 넣는다,

그 후 cd의 디렉토리들과 스택에 넣어진다.

▪ pushd dir에서 후속 호출은 dir에서 cd를 하며, 오직 push dir stack에 있다.

▪ popd stack에서 제외된 탑 디렉토리를 제거한다, 새로운 최상위가 생긴다.

그 후의 cds가 새로운 최상위 디렉토리다.

 

 

예를 들어, 4-4에 있는 이벤트의 시리즈를 고려한다.

그냥 당신은 자신의 홈 디렉토리에  로그인 했다고 가정하자.(/home/you)

Table 4-4. pushd/popd

.

Command

Stack contents                                             Result directory

pushd lizard

/home/you/lizard/home/you             /home/you/lizard

pushd /etc

/etc /home/you/lizard/home/you      /etc

popd

/home/you/lizard /home/you           /home/you/lizard

popd

/home/you                                    /home/you

popd

<empty>                                       (error)

 

우리는 공백으로 구분된 디렉토리의 목록을 포함하는 환경 변수를 스택으로 구현합니다.

 

당신이 로그인 할때, 당신의 디렉토리 스택은 null 문자로 초기화 되야 합니다.

이렇게 되려면, 당신의 .bash_profile에 이것들이 들어가야 한다:

DIR_STACK=""
   export DIR_STACK

 

만약 하나가 있다면, 사용자 환경 파일에 이것을 넣지 마십시오

export DIR_STACK이 알고 있는 모든 subprocesses들의 상태를 보장합니다:

당신이 오직 한번만 그것을 초기화한다면.

만약 당신이 환경 파일에 이 코드를 집어 넣으면, 아마도 당신은 원하지 않는

모든 subshell에서 다시 초기화 될 것이다.

 

다음, 우리는 pushd popd 함수의 구현이 필요합니다.

여기 우리의 초기 버전입니다:

 

pushd ( )
{
dirname=$1
DIR_STACK="$dirname ${DIR_STACK:-$PWD' '}"
cd ${dirname:?"missing directory name."}
echo "$DIR_STACK"
}

 

 

popd ( )
{
DIR_STACK=${DIR_STACK#* }
cd ${DIR_STACK%% *}
echo "$PWD"
}

 

저것은 일치되지 않는 코드의 공지입니다!

두 함수를 통하여 저들이 어떻게 작동하는지 보자, pushd의 시작과 함께.

첫 번째 라인은 단순히 가독성을 이유로 변수 dirname에 첫 번째 인수를 저장한다.

 

함수의 두번째 라인은 stack에 새로운 디렉토리를 넣는다

표현식 ${DIR_STACK:-$PWD ' '}$DIR_STACK으로 평가한다.

그것은 null이 아닌 경우 또는 $PWD''(현재 디렉토리와 공간) null 이라면.

표현식은 더블 쿼테이션 다음에 주어진 인수로 구성되어 있습니다,

 

DIR_STACK은 한 개의 공백으로 따르거나, 혹은 현재 디렉토리와 공백을 따른다.

현재 디렉토리에 후행 공간은 popd 함수에서 패턴 일치가 필요합니다:

stack의 각 디렉토리는 "dirname"의 형식으로 여겨집니다.

 

더블 쿼테이션이 지정되도록 이 모든 패키지 안에 하나의 문자열로 들어가게

다시 DIR_STACK에 지정  그러므로, 이 코드 라인은 특별한 초기화

케이스(stack이 비어있을때) 뿐만 아니라

더 많은 일반적인 케이스(그것은 비어 있지 않을때)를 다룬다.

 

세 번째 라인의 주 목적은 새 디렉터리로 변경하는 것입니다.

인수가 없을때 에러를 처리하는 :? 연산자:

만약 인수가 주어진다면, 표현식 ${dirname:? "missing directory name"}

$dirname로 평가된다.

그러나 인수가 주어지지 않은 경우, 쉘이 메시지를 출력한다.

pushd: dirname: missing directory name 그리고 함수로부터 종료된다.

 

마지막 라인은 그저 stack의 내용을 함축적으로 출력한다,

가장 왼쪽 디렉토리 모두 현재 디렉토리이며, stack의 상단.

(이것은 왜 우리가 별도의 디렉토리에서 공백을 선택하는지 보다는

PATH MAILPATH에서 더욱 관례적인 콜론이다.)

 

popd 함수는 쉘의 패턴 매칭 연산자의 또 다른 사용법을 만듭니다.

첫 라인은 # 연산자를 사용하여, 어떤 패턴 중 가장 짧은 일치를 삭제하려고합니다

DIR_STACK 의 값으로부터  "*" (공백 뒤에 아무것도 없는) 패턴 중 가장 짧은 일치를

삭제 하려고 합니다.

최상위 디렉토리와 STACK에서 삭제된 다음과 같은 공간의 결과 값

이것이 STACK의 밀려난 첫 번째 디렉토리의 끝에 공간이 필요한 이유입니다.

 

popd의 두 번째 라인은 DIR_STACK에서 패턴에 "*"(공백 다음에 아무것도 )으로

패턴 매칭 연산자 % %를 사용하여 가장 긴 매치를 삭제합니다.

이것은 cd에 인수로 최상위 디렉토리를 추출합니다,

그러나 아무 지정이 없기 때문에 그것은 DIR_STACK의 값에 영향을 주지 않는다.

마지막 라인은 확인 메시지를 출력합니다.

 

이 코드는 네 가지 방법에 결함이 있습니다.

첫째, 그것은 에러에 대한 규정이 없습니다. 예를 들면:

어떤 사용자가 존재하지 않거나 invalid된 디렉토리를 밀어 넣으려고 한다면?

어떤 사용자가 stack이 비어 있는데 pupd를 시도한다면?

 

코드의 테스트를 이해하여 저러한 에러 조건에 대한 반응을 어떻게 파악할 것인가

두 번째 문제는 당신이 쉘 스크립트에서 pushd를 사용하는 경우,

아무런 인수가 주어지지 않는다면 그것은 모두 종료됩니다;

${varname:?message}는 비대화식 쉘에서 항상 종료됩니다.

그러나, 함수가 호출되어 졌을때 대화형 쉘은 종료 되지 않습니다.

 

세 번째 문제는 단지 bash pushd의 일부 기능만을 구현하는 것입니다.

다음 챕터에서, 우리가 이 결함을 모두 극복하는 방법을 볼 수 있을 것입니다.

 

네 번째 문제인 코드의 경우  몇 가지 이유, 디렉터리명에 공백이 포함되어

아마 작동하지 않을 것입니다.

이 코드는 구분 기호 문자로 공백을 취급합니다.

현재 우리는 부족한 점들을 허용하지만, 뒤의 챕터에서 우리는 이것을 어떻게

극복해 나갈지 생각해 봅시다.

 

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

6. Command-Line Options and Typed Variables  (0) 2011.04.20
챕터 5. 흐름 제어  (0) 2011.04.19
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