koodev

Regular expression in sed

Programming

sed(1) 에서 사용되는 정규표현식에 대해서 알아보기 위해 Overview of Regular Expression Syntax 의 내용을 옮겨본다.

sed 를 잘 쓰기 위해서는 정규표현식(regexp)을 잘 알아야 한다. 정규표현식이란 주어진 문자열(subject string)에서 왼쪽에서 오른쪽으로 봐가면서 매칭되는 패턴을 의미한다. 정규표현식에서 대부분의 문자들(character)은 일반문자(ordinary)이다. 즉 이 문자들이 패턴 자체이고 주어진 문자열에서 해당 문자들이 나올 경우 매칭되게 된다. 예를 들어 아래와 같은 패턴은,

The quick brown fox

그 자체가 패턴이 된다. 하지만 정규표현식의 강력한 기능은 패턴을 작성함에 있어서 대치(alternatives)와 반복(repetitions)이 가능하다는 점이다. 정규표현식에서 대치와 반복은 특수문자를 써서 표현할 수 있다. 이렇게 특수문자로 쓰인 패턴은 그 자체가 패턴이 되는 것이 아니라 뭔가 특별한 용도를 위해 다르게 처리된다. 그럼 아래에 sed 에서 쓰이는 정규표현식 문법에 대해 정리해 보겠다.

Pattern Meaning
char 그 자체가 패턴이 되는 일반문자를 나타냄.
* * 앞에 나오는(preceding) 정규표현식이 0번 이상 매칭되는 패턴을 의미한다. '앞에 나오는 정규표현식'에는 ①일반문자, ②백슬래시(\)로 시작하는 특수문자, ③점(.), ④(아래에서 설명할)정규표현식 그룹, 그리고 ⑤대괄호로 묶여진 표현식 등이 될 수 있다. GNU 확장규칙(extension) 에서는 정규표현식이 * 뒤에서 나올(postfixed)수도 있다. 예를 들어, a**a* 랑 같다. POSIX 1003.1-2001에 의하면 * 은 어떤 정규표현식이나 서브익스프레션의 시작부분에 등장할 경우 별(*) 그 자신을 의미한다고 되어있다. 하지만 많은 non-GNU 유틸들은 이를 지원하지 않으며, 같은 상황에서 포터블 스크립트들은 \* 을 대신 사용한다.
\+ * 과 동일하지만 매칭되는 패턴의 개수가 1개 이상이다. GNU extension.
\? * 과 동일하지만 매칭되는 패턴의 개수가 0 또는 1개 이다. GNU extension.
\{i\} * 과 동일하지만 매칭되는 패턴의 개수는 정확히 i 번이다. 여기서 i는 10진수 정수지만 0~255 사이의 값을 사용하는것이 안전하다.
\{i,j\} i에서 j 개가 매칭된다.
\{i,\} i 번 이상 매칭된다.
\(regexp\) 내부 regexp를 그룹지을 때 사용한다. 그룹이 사용되는 예를 살펴보면 아래와 같다.
  • postfix operator로서 사용된다. 예를 들어, \(abcd\)* 은 'abcd' 문자열이 0번 이상 나오는 패턴이다. 단, 이는 POSIX 1003.1-2001 에 의한 GNU extenstion 이므로 non-GNU 유틸에서는 지원하지 않는다. 따라서 호환성을 고려한다면 안 쓰는게...
  • (뒤에서 설명할)Back reference 에 사용된다.
. 개행을 포함한 모든 캐릭터와 매칭된다.
^ 어떤 패턴에서 시작 부분의 널문자와 매칭된다. 이게 무슨 말이냐면, 꺾쇄문자(^) 다음에 오는 패턴은 패턴의 시작부분이라는 얘기다.

대부분의 sed 스크립트에서 패턴은 새 행이 시작될 때마다 초기화된다. 따라서 ^#include 의 경우 해당 행은 '#include' 라는 문자열로 시작되어야 하는 것이다. 만일 '#include' 앞에 공백이 있는 행일 경우에는 패턴매칭이 실패한다. 또한 이는 패턴 스패이스에서 원본 컨텐츠가 수정되지 않은 경우, 예를 들어 s 커맨드를 사용할 경우에만 유효하다.

^는 정규표현식이나 서브익스프레션에서 맨 앞에 나오는 특수문자로서도 쓰일 수 있다. POSIX에서 ^를 일반문자로 취급하는것을 허용하기는 하지만 포터블 스크립트에서는 서브익스프레션의 시작부분에 ^를 사용하는 것을 피하는 것이 좋을 것이다.
$ ^ 와 비슷하지만 맨 끝부분 패턴을 나타낸다. $ 도 역시 정규표현식이나 서브익스프레션에서 맨 끝에있는 특수문자를 가리키는데 쓰일 수 있다.
[list]
[^list]
list 내부의 단일 문자와 매칭된다. 예를 들어, [aeiou] 은 모든 모음 문자(vowels)와 매칭된다. listchar1-char2와 같은 표현식 (char1char2사이에 있는 모든 단일 캐릭터) 으로도 사용할 수 있다.

여기서 맨 앞에 ^이 나올 경우 list의 의미를 반전시킨다. 즉, list안에 있는 문자들을 제외한 단일 문자가 된다. 이스케이핑(escape) 관련하여 특수문자인 ]list 안에 포함시키기 위해서는 대괄호 닫음(])을 맨 처음에 넣으면 되고(^가 필요할 경우 ^를 먼저 넣는다), 특수문자 -list 안에 넣기 위해서는 대시(-)를 맨 처음이나 맨 나중에 넣는다. 특수문자 ^ 의 경우에는 첫 문자 다음에 넣도록 한다.

특수문자 $, *, ., [, \ 들은 list 안에서 특수문자(특별한의미)로 동작하지 않는다. 예를 들어 [\*] 이것은 '\' 이나 '*' 문자가 매칭된다. 왜냐하면 \list 안에서 특수문자(특별한의미)로 동작하지 않기 때문이다. 그렇지만 [.ch.], [=a=], [:space:] 들은 list 안에서 그별한 의미를 지니며 각각은 ch 이중문자(collating symbol; ch 체코어?), a 와 동등한 정렬순서를 갖는 문자집합(equivalence class), 공백을 나타내는 문자클래스(character class)를 의미한다. 즉, [list 안에서 ., =, : 등과 함께 쓰였을 경우 특별한 의미를 갖게된다. 또한 POSIXLY_CORRECT 모드가 아닐 경우 \n 이나 \t 과 같은 특수문자들도 list 안에서 특별한 의미를 갖는다. Escape 참고.
regexp1\|regexp2 regexp1 이나 regexp2 를 매칭한다(OR). 소괄호()를 사용하면 여러개를 이어서 사용할 수도 있는 것 같다. 매칭 과정은 왼쪽부터 시작해서 오른쪽으로 가면서 정규표현식들을 검사하고, 제일 처음 매칭된 정규표현식을 사용하게 된다. GNU extension 이다.
\digit 정규표현식 안에서 digit 번째의 소괄호 \(...\) 로 둘러싸인 서브익스프레션과 매칭된다. 이것을 back reference 라고 한다. 서브익스프레션은 암묵적으로 왼쪽에서 오른쪽으로 보면서 \( 이것이 나온 개수에 대하여 넘버링이 되어있다.
\n 개행 문자와 매칭된다.
\char char 와 매칭한다. 여기서 char$, *, ., [, \, ^ 이들 중에 하나이다. 이스케이핑(escape) 용도라는 말임. 참고로 C언어에서의 백슬래시 조합 특수문자 중에 \n 이나 \\ 이런것은 사용할 수 있다. 하지만 \t 는 대부분의 sed 구현에서 빠져있다. 즉, 탭 문자가 아니라 t로 매칭을 시도하게 된다.

정규표현식의 매칭방식은 좌에서 우로 나아가며 매칭하고, 같은 위치에서 두 개 이상의 매칭이 발견되었을 경우에는 긴 쪽을 택한다. 그래서 greedy(탐욕스러운) 하다라고도 한다.

아래는 정규표현식 예제들이다.

'abcdef'
쓰여진 그대로 'abcdef' 와 매칭됨.
'a*b'
0개 이상의 'a'와 그 다음에 'b'가 나오는 경우와 매칭된다. 예를 들면 'b'나 'aaaaab'.
'a\?b'
\? 는 0개 또는 1개 와 매칭되는 것이다. 따라서 'b' 또는 'ab' 와 매칭된다.
a\+b\+
\+ 는 1개 이상과 매칭되는 것이다. 따라서 1개 이상의 'a'와 이어서 1개 이상의 'b'가 오는 경우이다. 가장 짧게 매칭되는 경우가 'ab' 이고 'aaaab', 'abbbbb', 'aaaaaabbbbbb' 이렇게도 가능하다.
'.*/
'.\+'
둘 다 모든 캐릭터 문자열과 매칭되는 경우이다. 단, 위의 케이스는 널 스트링을 포함한 모든 문자열과 매칭되지만, 아래것은 최소한 하나의 문자는 들어있는 문자열과 매칭된다.
'^main.*(.*)'
'main'으로 시작하고, 소괄호로 둘러쌓인 문자열이 이어지는 경우이다. 'n'과 '('과 ')' 이 위치가 반드시 딱 붙어있어야 하는것은 아니다.
'^#'
'#'으로 시작하는 문자열과 매칭된다.
\\$
백슬래시(\) 하나로 끝나는 문자열과 매칭된다. 백슬래시를 두 개 썼지만 하나는 escaping 용도로 쓴 것이다.
'\$'
달러 기호 하나와 매칭된다. 여기에 쓰인 백슬래시도 escaping 용도로 쓰였다.
'[a-zA-Z0-9]'
환경변수 로케일이 C locale 인 경우, 모든 ASCII 문자 혹은 숫자와 매칭된다.
'[^ tab]\+
여기서 tab 은 탭 문자이다. 이 예제는 공백이나 탭 문자를 제외한 한 글자 이상의 캐릭터와 일치한다. 즉, 한 단어(word)를 의미한다.
'^\(.*\)\n\1$'
\(regexp\) 은 그룹을 이루고 back reference 로도 쓰인다. 이 예제는 두 개의 동일한 문자열 사이에 개행문자가 하나 들어간 꼴이다.
'.\{9\}A$'
9개의 문자에 이어서 A가 나오는 패턴이다.
'^.\{15\}A'
16개의 문자로 시작하고, 그 16개 중에 마지막 문자가 A인 경우이다.

'Programming' 카테고리의 다른 글

Bash에서 문자열 검색 조건식 만들기  (0) 2018.07.22
Truncate PWD in prompt (MacOS)  (0) 2018.04.29
How to terminate a background process  (0) 2018.04.24
macOS에 emacs ggtags 설치 및 설정  (0) 2017.10.17
Xcode에 assimp 올리기  (0) 2017.06.06