16 패턴 매칭 ( Pattern Matching )

GNU C 라이브러리는 두 종류의 형태(pattern)를 조화시키기 위한 도구를 제공한다: 보통의 표현들과 파일-이름 와일드카드. 라이브러리는 또한, 쉘이 하는 방법대로 변수를 확장하고 명령을 참조하고, 단어로 텍스트를 구문 분석하는 그러한 도구들을 제공한다.


16. 1 와일드카드 매칭 ( Winldcard Matching )

이 절은 특별한 문자열에 대응하여 와일드카드(wildcard) 패턴과 어떻게 매치할 것인지를 설명한다. 그 결과는 예, 또는 아니오 의 대답이다: 그 문자열이 패턴과 맞는지 틀린지. . . 여기에 설명된 모든 기호(sysbols)들은 모두 'fnmatch. h/에 선언되어 있다.

함수 : int fnmatch (const char *pattern, const char *string, int flags)

이 함수는 문자열 string 이 패턴 pattern 과 맞는지, 어떤지를 시험한다. 만일 그들이 서로 맞으면 0을 반환하고; 그렇지 않으면 영이 아닌 값 FNM_NOMATCH 를 반환한다. pattern 과 string인수들은 둘다 문자열이다. flags 인수는 세밀한 매칭(matchin)을 하기 위한 플래그 비트들의 조합니다. 정의된 플래그들은 아래를 참조하라.

GNU C 라이브러리에서, fnmatch는 "에러"를 발생시킬 수 없다. 그것은 항상 그 둘이 서로 맞는지에 대한 대답을 반환하기 때문이다. 그렇지만, fnmatch에서 파생된 다른 함수들은 때때로 "에러"를 보고할 것이다. 그들은 FNM_NOMATCH 과는 다른 0이 아닌 값을 반환함으로써 에러를 보고 할 것이다. 이들은 flags 인수에서 사용되는 유용한 플래그들이다.

FNM_FILE_NAME

파일이름과 맞추는 동안, '/' 문자를 특별히 취급하라. 만일 이 플래그가 설정되면, 패턴안의 와이드카드 구성들은 문자열에 있는 '/'과 매치할 수 없다. 그래서, '/'과 매치하기 위한 유일한 방법은 패턴안에 '/'를 명시하는 것이다.

FNM_PATHNAME

이것은 FNM_FILE_NAME 의 다른 이름으로; POSIX. 2로부터 왔다. 우리는 파일 이름으로 "경로이름"을 사용하지 않기 때문에 이 이름을 쓰는 것을 권장하지 않는다.

FNM_PERIOD

만일 문자열의 처음에 '. ' 문자가 나타나면 특별하게 취급하라. 만일 이 플래그가 설정되면, 패턴안의 와일드카드 구성은 첫 번째 문자가 '. '로 시작되는 문자열에서 '. '문자와 매치할 수 없다. 만일 당신이 FNM_PERIOD 와 FNM_FILE_NAME 을 모두 설정해 놓았다면, 문자열의 처음에 나타나는 '. ' 은 물론 '. '문자 다음에 나타나는 '/'문자도 특별한 취급을 한다. (쉘은 FN, _PERIOD 와 FNM_FILE_NAME 플래그를 파일이름과 매치하기 위해서 함께 사용한다. )

FNM_NOESCAPE

패턴안에 있는 '\'문자를 특별하게 취급하지 말아라. 보통, '\'가 바로 뒤에 따르는 문자를 인용하면, 원래 그 문자가 갖는 특별한 의미가 무효화된다. 인용이 가능했을 때, 패턴 '\?'는 패턴안의 의문부호가 보통의 문자처럼 행동하기 때문에 '?'과 매치된다. 만일 당신이 FNM_NOESCAPE을 사용하면, '\'도 보통의 문자가 된다.

FNM_LEADING_DIR

문자열에서 '/'뒤에 나타나는 문자들의 열을 무시하라; 이것은, 패턴과 매치되는 디렉토리 이름으로 시작하는 문자열인지를 시험함을 말한다. 만일 이 플래그가 설정되면, 패턴이 'foo*' 이거나 'foobar' 이면, 문자열 'foobar/frobozz' 과 매치될 것이다.

FNM_CASEFOLD

패턴에 비교되는 문자열의 대, 소문자 구분을 무시하라.


16. 2 Globbing

와일드카드의 전형적인 사용은 디렉토리 안에 있는 파일들과 맞추어서 매치되는 모든 파일들의 리스트를 만들기 위함이다. 이것은 globbing이라고 부른다.

당신은 fnmatch 를 사용해서 하나씩 디렉토리 엔트리들(entries)을 읽고, 테스트함으로써 이런 일을 할 수가 있다. 그렇지만, 속도가 느리다. (그리고 직접 서브디렉토리들(subdirectories)을 다루기 때문에 복잡하다. )

라이브러리는 편리한 와일드카드를 특별하게 사용하도록 하기 위해 glob 함수를 제공한다. glob 과 이 절에 있는 다른 심볼들은 'glob. h'에 선언되어 있다.

16. 2. 1 glob 호출하기

globbing의 결과는 파일 이름들(문자열)의 벡터(vector)이다. 이 벡터를 반환하기 위해서, glob은 구조체인 golb_t 라는 특별한 데이터타입을 사용한다. 당신이 glob에게 구조체의 주소를 주면, glob은 그 구조체의 각 필드를 채워서 당신에게 그 결과를 알린다.

데이터타입 : glob__t

이 데이터타입은 벡터를 가리키는 포인터를 저장한다. 더 자세하게 말하자면, 그것은 벡터의 주소와 그 크기를 갖고있는 레코드이다.

gl_pathc 벡터 안에 있는 요소들의 개수

gl_pathv 벡터의 주소. 이 필드는 char ** 형을 갖는다.

gl_offs gl_pathv 필드 안에 있는 명목상의 주소로부터 구한, 벡터의 첫 번째 실 요소의 offset. 다른 필드들과 달리, 이것은 glob으로부터 나온 출력이 아니라, 항상 glob에서 입력으로 사용된다. 만일 당신이 0이 아닌 offset을 사용하면, 벡터의 시작점으로부터 다른 요소들은 비어있는 채로 왼쪽에 존재한다. ( glob 함수는 널 포인터로 그들을 채운다. ) gl_offs 필드는 당신이 GLOB_DOOFFS 플래그들 사용할 때만 의미가 있다. 그렇지 않다면, offset은 이 필드 안에 무엇이 있던지 상관없이 항상 0이고, 첫 번째 실 요소는 벡터의 시작점에 있다.

함수 : int glob (const char *pattern, int flags, int (*errfunc) (const char *filename, int error-code), glob_t *vector_ptr)

glob 함수는 현재의 디렉토리에서 패턴 pattern을 사용해서 globbing을 한다. 그것은 새로이 할당된 벡터에 그 결과를 넣고, *vector'ptr에 이 벡터의 주소와 크기를 저장한다. flags인수는 비트플래그들의 조합이다; 플래그들에 대한 상세한 정보는 16. 2. 2절 [Flags for Globbing] 를 참조하라.

globbing의 결과는 파일이름들의 문자열이다. glob 함수는 각 결과를 위해서 워드(word) 단위의 문자열을 할당하고, 이들 문자열의 주소를 저장하기 위해서 char ** 형의 벡터를 할당한다. 벡터의 마지막 요소는 널 포인터이다. 이 벡터는 워드 벡터(word vector)라고 불린다. 이 벡터를 반환하기 위해서, glob는 *vector'ptr에 그 주소와 길이를 (널 포인터로 끝나는 것을 세지않은, 요소들의 개수) 저장한다. 보통, glob는 그들을 반환하기 전에 알파벳순으로 파일 이름들을 정렬한다. 당신은 만일 당신이 가능한 한 빨리 그 정보를 얻기를 원한다면 GLOB_NOSORT 플래그를 사용해서 이 기능을 이용하지 않을 수 있다.

만일 당신이 알파벳순으로 파일들을 처리한다면, 보통은 glob가 정렬하도록 하는 것이 좋고, 당신이 만든 응용프로그램을 사용하는 사용자들은 프로그램에 대한 가치를 느낄 것이다.

glob 함수가 성공하면 0을 반환하고, 그렇지 않다면 그것은 다음 에러코드 중 하나를 반환한다.

    GLOB_ABORTED

    디렉토리 개방에서 에러가 있었고, 그리고 당신이 GLOB_ERR 플래그를 사용했거나 errfunc가 0이 아닌 값을 반환했다. GLOB_ERR 플래그와 errfunc 에 대한 설명은 아래를 참조하라.

    GLOB_NOMATCH

    pattern이 현존하는 파일들과 아무 것도 맞지 않는다. 만일 당신이 GLOB_NOCHECK 플래그를 사용하면, 이 에러코드는 결코 나오지 않는다. 왜냐하면 이 플래그는 적어도 한 개의 파일이 패턴과 맞는 것이 있는 것처럼 glob이 가장하도록 하기 때문이다.

    GLOB_NOSPACE

    그 결과를 저장하기 위한 메모리 할당이 불가능하다. 에러가 발생하면, glob는 지금까지 발견한 pattern과 매치되는 것을 *vector`ptr에 정보를 저장한다.

 

16. 2. 2 Globbing 을 위한 플래그들

이 절은 glob 에게 flags 인수로 지정할 수 있는 플래그들을 설명한다. 당신이 원하는 플래그를 선택하고, 비트별 OR 연산자 | 을 사용해서 그들을 조합하라.

GLOB_APPEND

앞서서 호출한 glob이 만들어낸 워드 벡터에 새로운 워드벡터를 붙인다. 이것은 그들 사이를 공백으로 연결해서 여러 워드들을 효율적으로 확장할 수 있다. 붙이는 작업을 하려면, glob 호출동안 구조체 워드 벡터의 내용을 변화시켜서는 안 된다. 그리고 만일 당신이 glob의 처음 호출에서 GLOB_DOOFFS 를 설정하면, 당신이 그 결과들을 붙일 때도 반드시 그것을 설정해야만 한다. gl_pathv 에 저장한 포인터는 당신이 두 번째 glob을 호출한 이후에는 더 이상 유용한 정보가 아님을 기억하라. 왜냐하면, glob이 그 벡터의 위치를 바꾸기 때문이다. 그러므로 항상 매번 glob을 호출한 후에 바로 구조체 glob_t 에서 gl_pathv를 추출하라; 호출을 거쳐 그 포인터를 결코 저장하지 말아라.

GLOB_DOOFFS

워드 벡터의 시작점에 빈 슬롯을 남겨라. gl_offs는 얼마나 많은 슬롯을 남겨야 하는지를 알린다. 빈 슬롯은 널 포인터를 저장한다.

GLOB_ERR

즉시 포기하고 순서대로 읽혀져야만 하는 디렉토리를 읽는데 어떤 어려움이 있다면, 그 에러를 보고하라. 그와같은 어려움 들은 당신이 필요한 접근을 갖지 못한 디렉토리를 포함했기 때문일 것이다. 보통, glob는 그 디렉토리가 무엇이든지, 어떤 에러에도 불구하고, 계속 실행하려 시도한다. 당신은 glob를 호출할 때 에러-처리 함수 errfunc를 정함으로 해서 이것보다 더 많은 제어를 실행할 수 있다. 만일 errfunc 가 널 포인터가 아니라면, glob는 디렉토리를 읽을 수 없을 때 즉시 실행을 멈추지 않고; 대신 다음처럼 두 개의 인수를 사용해서 errfunc 함수를 호출한다:
(*errfunc) (filename, error-code)
filename인수는 glob이 개방할 수 없거나, 읽을 수 없었던 디렉토리의 이름이고, error-code는 glob에서 보고된 에러 값이다. 만일 에러처리 함수가 영이 아닌 값을 반환하면, glob는 즉시 멈춘다. 그렇지 않다면 계속 실행한다.

GLOB_MARK

만일 pattern이 디렉토리 이름과 매치되면, 그것을 반환할 때, 디렉토리의 이름에 '/'를 덧붙여라.

GLOB_NOCHECK

만일 pattern이 어떤 파일 이름과도 매치가 되지 않으면, 매치되는 파일 이름이 있는 것처럼 pattern 그 자체를 반환한다. (보통, pattern이 어느것과도 매치가 안될 때, glob는 매치되는 것이 아무 것도 없음을 반환한다. )

GLOB_NOSORT

파일 이름들을 정렬하지 말아라; 특별한 순서없이 그들을 반환하다. ( 실제로, 그 순서는 디렉토리에 있는 엔트리의 순서에 의존할 것이다. ) 정렬하지 않는 유일한 이유는 시간을 절약하기 위함이다.

GLOB_NOESCAPE

pattern들에 있는 '\' 문자를 특별하게 취급하지 말아라. 보통, '\'가 그 다음에 나타나는 문자들을 인용해서, 그 문자들이 갖는 특별한 기능을 없앤다. 인용(quoting)이 가능할 때, 패턴 '\?'은 오직 문자열 '?'로 매치되는데, 그 이유는 pattern에 있는 의문부호가 보통의 문자처럼 행동하기 때문이다.
만일 당신이 GLOB_NOESCAPE를 사용하면, '\'은 보통의 문자가 된다. glob가 반복적으로 fnmatch 함수를 호출함으로써 그 작업을 한다. fnmatch의 호출에서 FNM_NOESCAPE가 켬으로 해서 GLOB_NOESCAPE 플래그를 취급한다.


16. 3 정규식 매칭 ( Matching )

GNU C 라이브러리는 정규식을 매치하기 위한 두 개의 인터페이스를 제공한다. 하나는 표준 POSIX2 인터페이스이고, 다른 하나는 GNU 시스템이 오랫동안 가지고 있었던 것이다.

두 개의 인터페이스는 헤더파일 'regex. h'에 선언되어 있다. 만일 당신이 _POSIX_C_SOURCE 라고 정의하면, 오직 POSIX. 2의 함수들, 구조체, 그리고 상수들로만 선언되어 진다.

 

16. 3. 1 POSIX 정규식 컴파일

당신이 실제로 정규식을 매치하기 전에, 당신은 그것을 컴파일해야만 한다. 이것은 실제로 우리가 생각하고 있는, 기계 명령어로 바꾸는 그런 실제의 컴파일이 아니라 특별한 데이터 구조체를 생성하는 것을 말한다. 그러나 그것은 당신은 그 패턴을 "실행"이 가능하도록 만들려 하는 목적을 갖고 있다는 점에서 보통의 컴파일과 같다. (컴파일된 일반 표현식을 어떻게 매치하는지에 대한 정보는 16. 3. 3절 [Matching POSIX Regexps] 참조. )

컴파일된 정규식을 위한 특별한 데이터 타입은 아래와 같다.

데이터타입 : regex__t

이것은 컴파일된 정규식을 저장하는 오브젝트의 타입이다. 그것은 실제로 구조체이다. 당신은 당신의 프로그램에서 밑에 보여진 오직 하나의 필드만 가진다.
re_nsub 이 필드는 컴파일된 정규식에 존재하는 괄호로 묶여진 부정규식(subexpressions)의 개수를 저장한다.
그 구조체에는 여러 가지 다른 필드들이 있지만, 우리는 여기서 그들을 설명하지 않겠다. 왜냐하면 다른 필드들은 우리가 임의대로 건드릴 수 없고, 오직 라이브러리 함수에서만 그들을 사용하기 때문이다. 당신이 regex_t 오브젝트를 만든 후에, 당신은 regcomp를 호출함으로써 정규식을 컴파일할 수 있다.

함수 : int regcomp (regex_t *compiled, const char *pattern, int cflags)

regcomp 함수는 문자열과 매치시키는데 사용하는 regexec를 사용할 수 있도록 정규식을 데이터 구조체로 "컴파일"한다. 컴파일된 정규식의 형식은 매칭(matching)에 효율적이게 만들어졌다. regcomp는 *compiled에 그것을 저장한다.
당신은 regex_t 타입의 오브젝트를 할당한 다음 그 주소를 regcomp에게 주어라. 인수 cflags는 정규식의 구문과 의미들을 제어하는 다양한 옵션을 정할 수 있도록 허용한다. 16. 3. 2절 [Flags for POSIX Regexps] 참조. 만일 당신이 REG_NOSUB플래그를 사용한다면, regcomp는 어떻게 부정규식이 실제로 매치되는지를 기록하기 위해 필요한 정보를 컴파일된 정규식에서 생략한다. 이 경우, 당신은 regexec를 호출할 때 matchptr 과 nmatch인수로 0을 사용하는 것이 좋을 것이다. 만일 당신이 REG_NOSUB 를 사용하지 않는다면, 컴파일된 정규식은 어떻게 부정규식을 매치하는지를 기록하는 용량을 갖는다. 또한, regcomp는 얼마나 많은 부정규식 패턴을 가졌는지를 compiled->re_nsub 에 저장하여 당신에게 알린다. 당신은 그것을 부정규식 매치에 대한 정보를 저장할 곳을 할당하기 위해 얼마나 긴 배열을 할당할지를 정하기 위한 값으로 사용할 수 있다.
regcomp는 정규식을 컴파일하는데 성공하면 0을 반환하고; 그렇지 않으면 0이 아닌 에러코드( 밑에 설명된 )를 반환한다. 당신은 그 에러코드의 발생이유를 설명할 에러메시지를 만들기 위해서는 regerror를 사용할 수 있다; 16. 3. 6절 [Regexp Cleanup] 참조. 이것들은 regcomp가 반환할 수 있는 0이 아닌 값들이다.

REG_BADBR

정규식안에 유용하지 않은 `\{. . . \}` 구성이 있었다. 유용한 `\{. . . }\` 구성은 단일한 숫자, 또는 콤마로 분리된 오름차순으로 된 두 개의 숫자 중 하나를 포함해야만 한다.

REG_BADPAT

정규식에 구문에러가 있었다.

REG_BADRPT

`?'나 `*'와 같은 반복 연산자가 나쁜 위치에 있다(선행하는 아무런 부표현식도 없이 ).

REG_ECOLLATE

정규식이 유용하지 않은 대조(collating) 요소를 참조하였다. (문자열 대조를 위해서 현재의 지역에서 정의되지 않은 것. ) 19. 3절 [Locale Categories] 참조. )

REG_ECTYPE

정규식이 유용하지 않은 클래스 이름을 참조하였다.

REG_EESCAPE

정규식이 `\' 으로 끝났다.

REG_ESUBREG

`\digit' 구성에 유용하지 않은 숫자가 있었다.

REG_EBRACK

정규식 안에 균형이 맞지 않는 sqrare brackets([, ])가 있었다.

REG_EPAREN

연장된 정규식이 균형이 맞지 않는 괄호를 갖었거나, 기본 정규식이 균형이 맞지 않는 `\(' 와 `\)'를 가졌다.

REG_EBRACE

정규식이 균형이 맞지 않는 `\{' 와 `\}'을 가졌다.

REG_ERANGE

범위 표현식에서 끝점의 하나가 유용하지 않다.

REG_ESPACE

regcomp가 메모리를 다 써버렸다.

 

16. 3. 2 POSIX 정규식을 위한 플래그들

regcomp로 정규식을 컴파일할 때 피연산자 cflags에서 사용할 수 있는 비트플래그들에 대한 설명이다.

REG_EXTENDED

기본 정규식이 아닌, 연장된 정규식으로 패턴을 취급하라.

REG_ICASE

문자들을 매치할 때 대, 소문자 구분을 무시하라.

REG_NOSUB

match_ptr 배열에 저장한 내용들을 건드리지 말아라.

REG_NEWLINE

문자열에 있는 새줄문자를 여러 개의 라인으로 문자열을 나누는 역할을 하는 것처럼 취급한다, 그래서 `$'은 새줄문자 전에 매치할 수 있고, `^'은 새줄문자 후에 매치할 수 있다. 또한, 새줄문자와 매치하기 위해 `. '을 허용하지 않고, `[^. . . ]'을 허용하지 않는다. 그렇지 않다면 새줄문자는 다른 보통의 문자들처럼 작용한다.

 

16. 3. 3 컴파일된 POSIX 정규식을 매칭하기

일단 당신이 16. 3. 1절 [POSIX Regexp Compilation] 에서 설명된 것처럼 컴파일된 정규식을 가졌다면, 당신은 regexec를 사용해서 문자열과 그것을 매치할 수 있다. 만일 정규식이 `^' 나 `$' 와 같은 고정문자들을 표함하고 있지 않다면, 문자열 안에 어디에서든지 매치되는 것은 성공으로 센다.

함수 : int regexec(regex_t *compiled, char *string, size_t nmatch, regmatch_t matchptr [], int eflags)

이 함수는 문자열을 컴파일된 정규식*compiled와 매치하려 시도한다. regexec 는 만일 정규식이 매치되면 0을 반환하고; 그렇지 않다면 0이 아닌 값을 반환한다. 0이 아닌 값들이 무엇을 의미하는지 밑 테이블에 설명해 놓았다.
당신은 0이 아닌 값이 발생하는 이유를 설명할 에러 메시지를 생성하도록 regerror 함수를 사용할 수 있다; 16. 3. 6절 [Regexp Cleanup] 참조. 인수 eflags는 다양한 옵션들을 가능하도록 하는 비트플래그들의 집합인 워드이다. 만일 당신이 문자열이 정규식과 매치된 곳이 실제로 어느 부분인지 또는 그 부표현식은 무엇인지를 알기를 원한다면 인수 matchptr 과 nmatch를 사용하라.
그렇지 않다면 nmatch에는 0을 주고 matchptr에는 NULL을 주어라. 16. 3. 4절 [Regexp Subexpressions] 참조. 당신이 정규식을 컴파일했을 때 그 컴파일된 정규식은 실제로 그것이 현재 위치한 지역에 맞도록 정규식을 매치해야만 한다. regexec 함수는 eflags 인수에서 다음 플래그들을 받아들인다.

REG_NOTBOL

한 라인의 시작이 정해진 문자열로 시작하는지를 신경 쓰지 말아라; 즉, 어떤 텍스트의 앞에 무엇이 선행해야만 한다는 어떤 가정도 만들지 말아라.

REG_NOTEOL

한 라인의 끝이 정해진 문자열로 끝나는지를 신경 쓰지 말아라; 즉, 어떤 텍스트의 뒤에 무엇이 따라와야만 한다는 어떤 가정도 만들지 말아라.

regexec가 반환할 수 있는 0이 아닌 값들에 대한 설명.

    REG_NOMATCH

    패턴이 문자열과 매치되는 것이 없다. 이것은 사실상 에러는 아니다.

    REG_ESPACE

    regexec가 메모리를 다 써버렸다.

 

16. 3. 4 부표현식 ( Subexpressions ) 과 매치한 결과

regexec함수가 패턴의 괄호로 묶인 부문자열을 매치할 때, 그것은 매치하려는 문자열의 부분들을 기록한다. 그것은 구조체 regmatch_t 형인 요소를 가진 배열 안의 offsets 에 저장하여 그 정보를 반환한다. 그 배열(index 0)의 첫 번째 요소는 전체 정규식과 매치된 문자열의 부분을 기록한다. 배열의 다른 요소들은 단 하나의 괄호로 묶인 부표현식과 매치된 부분의 처음과 끝을 기록한다.

데이터타입 : regmatch__t

이것은 당신이 regexec 함수에서 사용하기 위한 배열 matcharray의 데이터타입이다. 그것은 다음과 같은 두 개의 필드들을 갖고 있다.
rm_so 문자열에 있는 부문자열의 시작점의 offset. 그 부분의 주소를 얻기 위하여 문자열에 이 값을 더하라.
rm_eo 문자열에 있는 부문자열의 끝점의 offset

데이터타입 : regoff__t

regoff_t는 signed integer 형의 다른 이름이다. regmatch_t의 필드들은 regoff_t의 형을 갖는다. regmatch_t 요소들은 부표현식의 위치에 대한 것이다; 첫 번째 요소( index 1)는 매치된 첫 번째 부표현식이 어디에 있는지를 기록하고, 두 번째 요소는 두 번째 부표현식을 기록하고, 등등. 부표현식의 순서는 그들이 나타난 순서이다.

당신이 regexec를 호출할 때, 얼마나 긴 matchptr 배열이 있는지, nmatch 인수를 가지고 정해야한다. 이것은 regexec함수에게, 저장하기 위한 얼마나 많은 요소들이 있는지를 알린다. 만일 실제로 정규식이 nmatch보다 더 많은 부표현식을 갖는다면, 당신은 그들의 나머지에 대한 offset정보를 제대로 얻지 못한 것이다. 그러나 이것은 그 pattern이 어떤 특별한 문자열과 매치되는지 또는 매치되지 않는지에 대한 사실을 변경하지 않는다.

만일 당신이 regexec 함수가 매치된 부표현식에 대한 어떤 정보도 반환하기를 원하지 않는다면, 당신은 nmatch에 0을 넣거나, 또는 regcomp로 pattern을 컴파일할 때 REG_NOSUB 플래그를 사용하라.

 

16. 3. 5 부표현식 매치하기의 복잡함

때때로 부표현식은 아무 문자도 없는 부문자열(substring)을 매치한다. 이것은 'f\(o*)\'와 문자열 `fum'을 매치할 때 발생한다. (그것은 실제로는 단지 `f'를 매치한다. ) 이 경우, offsets의 양쪽은 널 부문자열이 발견된 곳을 가리킨다. 이 예에서, offsets은 둘다 1이다.

때때로, 전체 정규식은 전혀 부표현식을 사용하지 않고 매치할 수 있다. 예를 들어, `ba\(na\)*'이 'ba'와 매치할 때, 괄호안에 있는 부표현식은 사용되지 않는다. 이런 일이 발생할 때, regexec는 부표현식을 위한 요소의 필드에 -1을 저장한다.

때때로 전체 정규식을 매치하기는 한 번보다 더 많은 특별한 부표현식을 매치할 수 있다. 예를 들어, `ba\(na\)*'이 문자열 `bananana'와 매치할 때, 괄호안의 부표현식은 세 번 매치된다. 이런 일이 발생할 때, regexec는 보통, 부표현식과 매치된 문자열의 마지막 부분의 offsets을 저장한다. `bananana'의 경우에 이들의 offsets은 6과 8이다.

그러나 마지막으로 매치된 것은 항상 문자열의 마지막에 있는 매치된 것이 아니다. 그것은 매치하기 위한 마지막 기회에서 우선권을 가진 것이라고 하는 것이 더 정확하다. 이것이 의미하는 것은 하나의 부표현식이 다른 것에서 발견됐을 때, 내부 부표현식을 위해 보고된 결과는 밖의 부표현식과의 마지막 매치에서 무슨 일이 일어났는지에 대해 영향을 받는다. 예를 들어, `\(ba\(na\)*s \)*' 을 문자열 `bananas bas '와 매치하는 것을 고려해보자. 첫 번째, 내부 표현식과 실제로 매치된 것은첫 번째 단어의 끝에 가까이 있다. 그러나 다시 두 번째 단어를 고려해보면, 그곳에는 매치되는 것이 아무 것도 없다. regexec는 "na" 부표현식이 사용되지 않은 것으로 보고한다.

다른예로 `\(ba\(na\)*s \|nefer\(ti\)* \)*'을 'bananas nefertiti'와 매치할 때 이 규칙이 적용되는지를 알아보자. "na" 부표현식은 첫 번째 단어에서 매치되는 것이 있지만, 두 번째 단어에서는 매치되는 것이 없다. 다시 한번, 밖의 부표현식의 두 번 반복은 첫 번째를 무시하고, 두 번째 반복에서 "na" 부표현식은 사용되지 않았다, 그래서 regexec는 "na" 부표현식이 사용되지 않았음을 보고한다.

 

16. 3. 6 POSIX Regexp 매치하기 소거

당신이 컴파일된 정규식을 다 사용했을 때, 당신은 regfree를 사용해서 그 저장공간을 해제할 수 있다.

함수: void regfree (regex_t *compiled)

regfree를 호출해서 *compiled가 가리키고 있는 모든 저장영역을 해제할 수 있다. 이것은 이 매뉴얼에서는 설명하지 않았지만, 구조체 regex_t의 다양한 내부적 필드들을 포함하고 있다. regfree는 오브젝트 *compiled 자체를 해제하지 않는다.
당신은 다른 정규식을 컴파일하기 위해 그 구조체를 사용하기 전에 regfree를 사용해서 구조체 regex_t 안에 있는 공간을 항상 해제해야한다. regcomp나 regexec에서 에러가 났을 때, 당신은 에러메시지를 출력하기 위해서 regerror함수를 사용할 수 있다.

함수: size_t regerror (int errcode, regex_t *compiled, char *buffer, size_t length)

이 함수는 에러코드 errcode를 위한 에러메시지 문자열을 생성하고, buffer에서 시작하는 메모리의 length 바이트 안에 그 문자열을 저장한다. 컴파일된 인수를 위해서, regcomp나 regexec에서 작업되었던 같은 컴파일된 정규식 구조체를 공급한다. 선택적으로, 당신은 compiled를 위해서 NULL을 공급할 수 있다; 당신은 여전히 의미 있는 에러메시지를 얻을 것이다, 그렇지만, 그것은 상세하지 않을 것이다.
만일 그 에러메시지가 length 바이트의 길이에 맞을 수 없다면(널 종료문자를 포함해서), 그러면 regerror는 그것은 자른다. regerror 이 저장한 문자열은 심지어 그것이 잘렸을 때라도 널 종료문자를 저장한다. regerror의 반환값은 전체 에러메시지를 저장하기 위해 필요한 최소 길이이다. 만일 이것이 length보다 적다면, 에러메시지는 잘리지 않고, 당신은 그것을 사용할 수 있다. 그렇지 않다면, 당신은 더큰 버퍼를 잡아서 다시 regerror를 호출해야한다. 이곳에 regerror를 사용하는 함수가 있는데, 에러메시지를 위한 버퍼를 항상 동적으로 할당한다.
char *get_regerror (int errcode, regex_t *compiled)
{
size_t length = regerror (errcode, compiled, NULL, 0);
char *buffer = xmalloc (length);
(void) regerror (errcode, compiled, buffer, length);
return buffer;
}


16. 4 쉘-스타일 단어 확장

단어 확장은 단어들로 문자열을 분리하고, 그것을 쉘이 하는 것처럼 변수, 명령, 그리고 와일드카드로 해석하는 것을 의미한다. 예를 들어, 당신이 `ls -l foo. c'라고 쓸대, 이 문자열은 세단어, `ls', '-l', `foo. c'로 분리된다. 이것은 단어 확장의 가장 기본적인 함수이다.

당신이 `ls *. c'라고 쓸대, 이것은 단어 `*. c'가 어느 수의 파일이름들과 대치될 수 있기 때문에 많은 단어들이 될 수 있다. 이것은 와일드카드 확장이 호출되는데, 그것은 또한 단어 확장의 일부분이다. 당신이 당신의 경로를 프린트하기 위해서 `echo $PATH'를 사용할 때, 이것도 또한 단어 확장의 일부분인 변수 치환이 이용된다. 프로그램들은 라이브러리 함수 wordexp를 호출하여서 쉘처럼 단어 확장을 수행할 수 있다.

 

16. 4. 1 단어 확장의 단계

단어 확장이 단어들의 열(suquence)에 적용될 때, 그것은 여기에 보여진 순서를 따라서 변환을 수행한다.

1. 틸드(~) 확장: `~foo'는 `foo'의 홈 디렉토리로 대치된다.
2. 다음, 세 개의 다른 변환들은 왼쪽에서 오른쪽으로, 동등한 결합순서로 적용된다.
  • 변수 치환: 환경변수들은 `$foo'처럼 참조를 위해서 대치된다.
  • 명령 치환: ``cat foo`' 와 그와 동등한 `$(cat foo)'와 같은 것들은 내부 명령을 통해서 출력으로 대치된다.
  • 산술적 확장 : `$(($x-1))'과 같은 것은 산술적 계산의 결과로 대치된다.
3. 필드 분리하기 : 텍스트를 단어로 분리한다.
4. 와일드카드 확장 : `*. c'와 같은 구석은 `. c'로 끝나는 파일이름들의 리스트로 대치된다. 와일드카드 확장은 동시에 전체 단어로 적용되고, 그들 단어들이 있는 0개의 파일, 또는 많은 파일들로 대치한다.
5. 인용 제거: 문자열-인용의 제거, 그들은 적당한 때에 변환을 금지함으로써, 그들의 작업이 수행된다.

이들 변환들에 대한 상세한 정보, 그리고 그들을 사용한 구성들을 어떻게 쓸것인가에 대한 것들은 BASH 매뉴얼을 참조하라.

 

16. 4. 2 wprdexp 호출하기

단어 확장을 위한 모든 함수들, 상수들, 그리고 데이터타입들은 헤더파일 'wordexp. h'에 선언되어 있다. 단어 확장은 단어들(문자열)의 벡터를 생성한다. 이 벡터를 반환하기 위해서, wordexp는 구조체인 wordexp_t라는 특별한 데이터타입을 사용한다. 당신이 그 구조체를 wordexp함수에 주면, 그것은 그 결과를 알리기 위해서 그 구조체의 필드를 채운다.

데이터타입 : wordexp_t

이 데이터타입은 워드 벡터를 가리키는 포인터를 저장한다. 좀 더 자세하게 말하면 워드 벡터의 주소와 그 크기를 기록한다.
we_wordc : 벡터에 있는 요소들의 개수
we_wordv : 벡터의 주소. 이 필드는 char ** 타입을 갖는다.
we_offs : we_wordv 필드에 있는 명목상의 주소로부터 구한 벡터의 첫 번째 실 인수의 offset. 다른 필드들과 달리, 이 필드는 wordexp함수에서 항상 입력으로 사용된다. 만일 당신이 0이 아닌 offset을 사용하면, 벡터의 시작점에서 많은 요소들은 비어있는 왼쪽에 있다. (wordexp함수는 널 포인터로 그들을 채운다. ) we_offs 필드는 당신이 WRDE_DOOFFS 플래그를 사용했을 때만 유용하다. 그렇지 않다면, offset은 이 필드 안에 무엇이 있는지 상관없이 항상 0이고, 첫 번째 실인수는 벡터의 시작점에 있다.

함수: int wordexp (const char *words, wordexp_t *word-vector-ptr, int flags)

단어들로 이루어진 문자열에서 단어 확장을 수행하고, 그 결과를 새로이 할당된 벡터에 넣고, 그리고 이 벡터의 크기와 주소를 *word-vector-ptr에 저장한다. 인수 flags는 비트플래그들의 조합이다; 플래그들에 대한 상세한 정보는 16. 4. 3절 [Flags for Wordexp] 를 참조하라.
당신은, 그들이 인용되지 않았다면 문자열 안에 `|&; <>' 문자들을 사용하지 못한다; 새줄도 마찬가지. 만일 당신이 인용 없이 이들 문자들을 사용한다면, 당신은 WRDE_BADCHAR 에러코드를 얻을 것이다. 그들이 인용되지 않았거나 또는 단어 확장 구성의 부분이 아니라면 괄호나, 쌍으로 이루어진 것들을 사용하지 말아라. 만일 당신이 인용 문자들 `'"`' 을 사용한다면 그들은 균형이 맞는 한 쌍이 되어야 한다. 단어 확장의 결과는 단어들의 열이다.
wordexp함수는 결과로 나온 단어를 위한 문자열을 할당하고, 이들 문자열의 주소를 저장하기 위해서 char ** 타입의 벡터를 할당한다. 벡터의 마지막 요소는 널 포인터이다. 이 벡터는 워드벡터라 불린다. 이 벡터를 반환하기 위해서, wordexp는 그 주소와, 그 길이(종료 널 포인터를 셈하지 않은 요소들의 개수)를 *word-vector-ptr에 저장한다.
만일 wordexp가 성공하면 0을 반환하고, 그렇지 않으면 다음 에러코드 중 하나를 반환한다.

WRDE_BADCHAR

입력 문자열 단어들이 인용이 없는 `|'와 같이 유용하지 않은 문자를 포함하고 있다.

WRDE_BADVAL

입력 문자열이 정의되지 않은 쉘 변수를 참조하였고, 당신이 그와같은 참조를 금하기 위해서 WRDE_UNDEF 플래그를 사용하였다.

WRDE_CMDSUB

입력 문자열이 명령 치환을 사용했는데, 당신이 명령 치환을 금하기 위해서 WRDE_NOCMD 플래그를 사용하였다.

WRDE_NOSPACE

그 결과를 저장하기위한 메모리를 할당하기가 불가능하다. 이 경우, wordexp는 할당할 수 있는 만큼 그 결과의 일부를 저장할 수 있다.

WRDE_SYNTAX

입력 문자열에 구문에러가 있었다. 예를 들어 매치되지 않는 인용 문자가 구문에러이다.

함수: void wordfree (wordexp_t *word-vector-ptr)

*word-vector-ptr이 가리키고 있는 문자열과 벡터를 위한 저장공간이 해제된다. 이것은 *word-vector-ptr 자체를 해제하지 않고, 단지 그것이 가리키고 있는 데이터만을 해제한다.

 

16. 4. 3 단어 확장을 위한 플래그들

이 절은 wordexp에서 사용되는 flags인수로 지정할 수 있는 플래그들을 설명한다. 당신이 원하는 플래그들을 선택하고, 그들을 연산자 |을 사용해서 조합하라.

WRDE_APPEND

바로 전에 호출한 wordexp가 생성한 단어의 벡터에 이번 확장으로부터 생성된 단어들을 붙여라. 이것은 그들 사이가 공백으로 분리되어져서, 여러 단어들을 효율적으로 확장할 수 있는 방법이다. 덧붙이기 위한 일련의 작업에서, 당신은 wordexp의 호출사이에 워드 벡터 구조체의 내용을 갱신하지 말아야만 한다. 그리고, 만일 당신이 wordexp의 첫 번째 호출에 WRDE_DOOFFS를 설정하면 당신은 그 결과들을 붙일 때에도 또한 그것을 설정해야만 한다.

WRDE_DOOFFS

단어들의 벡터의 시작점에 빈 슬롯들을 남겨라. we_offs 필드는 얼마나 많은 슬롯들을 남겨야하는지를 알린다. 빈 슬롯들은 널 포인터를 포함한다.

WRDE_NOCMD

명령 치환을 하지 말아라. 만일 입력이 명령 치환을 요청하면, 에러를 출력한다.

WRDE_REUSE

앞서서 호출한 wordexp에서 만들어진 워드 벡터를 재사용 하라. 새로운 벡터를 할당하는 대신에 wordexp는 이미 존재하고 있는( 필요한 만큼 그것을 크게 만들어서) 벡터를 사용할 것이다. 그 벡터를 이동할지도 모른다는 것을 기억하라, 그래서 오래된 포인터를 저장하고, wordexp를 호출한 후에 그것을 다시 사용하는 것은 안정적이지 않다. 당신은 매번 호출마다 새로운 we_pathv를 추출해야만 한다.

WRDE_SHOWERR

명령 치환에 의해 만들어진 명령으로 실행된 결과로 나온 프린트된 에러메시지를 보여라. 더 정확히, 현재 프로세스의 표준 에러 출력 스트림을 계승하도록 이들 명령들에게 허용한다. 디폴트로, wordexp는 모든 출력을 버린 표준 에러 스트림을 이들 명령들에게 준다.

WRDE_UNDEF

만일 입력이 정의되지 않은 쉘 변수를 참조한다면 에러가 출력된다.

16. 4. 4 wordexp Example

wordexp를 사용해서 여러 문자열을 확장하고, 그 결과들을 쉘 명령을 실행하기 위해 사용하는 예제가 있다. 확장들을 분리하기 위해 사용한 WRDE_APPEND와 wordexp에 의해 할당된 공간을 해제하기 위한 wordfree의 사용도 보여주고 있다.

int
expand_and_execute (const char *program, const char *options)
{
wordexp_t result;
pid_t pid
int status, i;
/* 그 프로그램을 실행하기 위해서 문자열을 확장하라 */
switch (wordexp (program, &result, 0))
{
case 0: /* Successful. */
break;
case WRDE_NOSPACE:
/* 만일 그 에러가 WRDE_NOSPACE라면 아마도 결과의 일부분이 할당되어진다. */
wordfree (&result);
default:
return -1;
/* 어떤 다른 에러 */
}
/* 인수를 위해 정해진 문자열을 확장하라. */
for (i = 0; args[i]; i++)
{
if (wordexp (options, &result, WRDE_APPEND))
{
wordfree (&result);
return -1;
}
}
switch( pid = fork()) {
case -1 :
status = -1;
break;
case 0 : /* 이것은 자식 프로세스이다. 그 명령을 실행하라. */
execv (result. we_wordv[0], result. we_wordv);
exit (EXIT_FAILURE);
defautl : /* 이것은 부모 프로세스이다. 자식이 완수하도록 기다려라 */
if (waitpid (pid, &status, 0) != pid)
status = -1;
}
wordfree (&result);
return status;
}

실제로, wordexp는 서브쉘(subshell)로 실행되기 때문에, 문자열에서 그들 사이를 공백으로 분리하고 쉘 명령 `sh -c'를 사용해서 실행하는 것보다는 빠르다.