본문 바로가기
공부/리눅스, 쉘스크립트

[쉘 스크립트] 정규 표현식 (grep)

by PraNi_ 2018. 5. 2.
반응형

정규표현식

텍스트에서 패턴을 인식하는 심볼 표기법입니다. 쉘의 와일드카드 방식보다 더 큰 규모로써 도구마다 정규표현식이 조금씩 다릅니다.

대게 POSIX(이식 가능 운영 체제 인터페이스) 표준, 정규표현식을 구분합니다.


grep

명령어

- grep [옵션] [패턴] [파일] 로 표현합니다.

- 파일(파이프)에서 지정된 정규 표현식과 일치하는 행을 찾아 출력합니다.

옵션

-i : 대소문자 구분안함, --ignore-case

-v : 반전 매치, --invert-match

-c : 정규표현식에 매치하는 행의 수 출력, --count

-l : 일치하는 행을 포함하는 각각의 파일 이름을 출력, --files-with-matches

-L : 일치하는 행이 없는 파일의 이름을 출력, --files-without-match

-n : 일치하는 행 앞에 파일의 행 번호를 붙여 출력, --line-number

-h : 복수 파일 검색에서, 파일명을 출력에서 숨김, --no-filename


기본 예문)

1. grep으로 root 라는 문장을 /etc/passwd에서 찾아내기

루트가 써져있는 행만 출력이되어 보여주는 것을 볼 수 있습니다.


2. ls  /usr/bin | grep zip으로 파이프를 삽입해 zip이 포함된 문자열을 찾습니다.

파이프를 삽입해 명령어를 사용해본결과, zip이 포함된 파일명을 찾을 수 있었습니다.


계속

계속해서 쭉쭉나가자면, 정규표현식 = 리터럴(상수문자) + 메타문자다. 라고 되어있는데요.

여기서 메타문자는 ^ $ . [] - () \ | ? * + {} 입니다.

주의해야할 것은 메타문자는 쉘의 확장에 사용되므로, 확장 방지를 위해 메타문자를 따옴표로 묶어야(인용)합니다.

1) 앵커 메타문자 : ^(행의시작을 나타냄), $(행의 끝을 나타냄)

간단한 예제로 설명하도록 하겠습니다. 우선 ls 출력을 이용, 텍스트파일로 저장해보겠습니다.

위 사진처럼 출력 내용을 텍스트로 저장해줍니다. 그리고 디텍토리에 정상적으로 파일이 저장되었는지 확인해봅니다.

물론 cat을 이용해 내용을 들여다보고 확인해봐도 됩니다.




다음은 이 출력된 텍스트 파일을 가지고 grep을 사용해봅니다.

(1) grep -h '^pas' dirlist*.txt

행시작이 'pas'로 시작되는 것을 dirlist들의 파일 안에서 내용을 찾아달라는 명령이죠.

-h는 복수파일에서 찾아달라는 옵션입니다.

보시는 바와 같이 pas로 시작하는 4개의 문장이 검색되었음을 확인할 수 있습니다.


(2) grep -h 'zip$' dirlist*.txt

이 것은 문장의 끝이 'zip'으로 끝나는 것을 찾아달라는 명령입니다.

마찬가지로 옵션은 동일합니다.

zip으로 끝나는 문장이 검색되었음을 출력하여 알려줍니다.

(3) grep -h '^zip$' dirlist*.txt

그럼 메타문자 ^,$를 둘 다 입력하게되면 어떻게 될까요?

단어열중 일부만 입력하면 당여니 출력되지않는 것을 확인할 수 있습니다.

그래서 정확한 검색명을 입력해야만 출력이 이루어지는 것을 확인할 수 있습니다.


2) 모든 문자

[ . ] 을 사용하게되면 어떤 문자든 일치시킨다는 의미입니다.

옵션은 그대로 -h를 주고,

ex) grep -h '.user' dirlist*.txt 를 입력하게되면,

위 사진처럼 user를 포함한 모든 문자가 출력됩니다.


3) 대괄호[] 표현식과 문자클래스

대괄호 안에 놓인 메타문자는 특수 의미가 사라집니다. 일반 상수(리터럴)로 취급됩니다.

하지만, 대괄호 안의 ^는 부정이되고, -는 문자 범위를 의미하게됩니다.

ex1) grep -h '[dw]user' dirlist*.txt

이 문형의 같은 경우에는 대괄호를 풀어 해석하면 'duser'나 'wuser'를 포함하는 행을 출력해달라는 의미입니다.

사용을 해보면

로 포함된 파일내 값이 출력이 되는 것을 확인할 수 있습니다.


ex2) 그럼 [dw] 내에 ^를 포함해서 '[^dw]user'를 검색해보면 어떻게될까요? 부정이된다고 그랬죠?

그럼 따지고 봤을때 'duser'나 'wuser'는 포함되지 않아야합니다.

위 설명과 같이 위 사진도 말그대로 출력되었죠?


ex3) 다음은 문자범위입니다. 문자 범위는 아까 설명과 같이 [ - ]로 표현할 수 있다고 했습니다. 예문을 볼까요?

먼저 입력을 해봅시다. grep -h '^[A-Z]' dirlist*.txt 입니다.

주의하셔야할게 여기서 지금 ^는  [] 밖에 있으므로 부정표현이 아니라 풀어쓰자면 A에서 Z까지의 대문자로 시작하는 내용을 출력해라 입니다.

이 부분 유의하셔서 입력하셔야해요.

이와같이 대문자로 시작하는 내용이 출력되었습니다.

내용은 각자 내부사정에따라 다를 수 있으니 출력값이 정확하게 내가 원하고자 하는 필터링이 되었는가 한번 더 확인해보세요.




ex4) grep -h '^[A-Za-z0-9]' dirlist*.txt

이건 해석을 해보자면 영대문자, 영소문자, 숫자 0부터 9까지를 포함하는 모든 내용을 출력해라 입니다.

근데 이렇게되면 그냥 전부다 출력되는 것이기 때문에 파이프[ | ] 를 이용해서 스페이스바로 넘겨보면 됩니다.

의미는 그다지 없어보입니다.


POSIX 문자클래스

여기서는 문자클래스를 짚어보도록 하겠습니다.
[:alpha:] #알파벳 영대소문자를 의미합니다. [A-Za-z]
[:alnum:] #알파벳과 숫자를 의미합니다 [A-Za-z0-9]
[:digit:] #숫자 0~9를 의미합니다. [0-9]
[:xdigit:] #16진수 숫자를 의미합니다 [0-9A-Fa-f]
[:lower:] #영소문자를 의미합니다. [a-z]
[:upper:] #대문자를 의미합니다 [A-Z]
[:blank:] #스페이스와 탭 문자를 의미합니다. [\t]
[:space:] #공백문자, 스페이스, 탭, 캐리지리턴, 수직탭, 폼피드를 의미합니다 [\t\r\n\v\f]

간단한 예로 한번 볼까요. 또, dirlist 파일을 이용하면 됩니다.
ex) grep '^[[:upper:]]' dirlist*.txt
를 사용하면되고, 대문자로 시작하는 행을 찾는 겁니다.

보이는 바와 같이 대문자로 시작하는 행이 검색되어 출력됨을 알 수 있습니다.


Alternation(얼터네이션) [정규식 확장가능!]

확장 정규표현식이 가능한데, 그 것은 아까 한번 써봤던 파이프 [ | ] 를 사용하면 됩니다.

이번엔 echo를 조금 이용해보기도 할게요.

ex) echo "AAA" | grep AAA

에코 기능을 이용해 AAA를 입력했을때 grep에서 AAA를 받아보고 일치하면 출력시켜주는 내용입니다.

조금더 나아가서,

ex2) echo "AAA" | grep -E 'AAA|BBB'를 하게 되면 어떻게 될까요?

옵션의 [-E]는 확장 정규표현식 사용 옵션을 나타내주는 것입니다.

혹시나 싶어서 [-E]를 빼고 한번 입력해봤는데요. 출력이 되지않았습니다.

AAA거나 BBB인 것이 에코입력에서 일치하면 출력해주는 것입니다.

보셨죠? AAA를 입력하든 BBB를 입력하든 출력을 해주고 있습니다.

허나, CCC를 입력하게되면 출력이 안되는것보이시죠?

ex3) grep -Eh '^(gz|dw)' dirlist*.txt

이건 얼터네이션과 다른 정규표현식 요소와 결합할때 사용할 수 있는 응용인데요.

확장 정규표현식이므로 [-E]도 쓰고 [-h]도 써주어 아까 많이 출력해놨던 복수파일을 실행해봅니다.

해당 문자열로 시작되는 파일을 검색해 출력해줍니다.




수량 한정자(확장 정규표현식 기능) : ?, *, +, {}

? : (앞의 항목이 없거나 한번만 나타남) 앞의 요소는 선택적이라는 의미입니다.

ex) 전화번호로 한번 예를 들어보죠. (000) 000-0000 또는 000 0000-0000 이라는 양식을 가지는 전화번호가 있다고 생각해봅시다.

정규식은 ^\(?[0-9][0-9][0-9]\)? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$로 정의할 수 있습니다.

저기서, 괄호를 문자로 표기할때는 앞에 \(역슬래시) 기능을 이용해 표시해주어야합니다, 해석을 하면 일단 [0-9]는 숫자 한자리가 되는건데,

아까 말씀드렸듯, 앞의 항목이 없거나 한번만 나타나는 것을 의미하거나 선택적이라는 의미를 나타내는 의미에서 [ ? ]를 쓴다고 했죠.

그래서 괄호 또는 문자 뒤에다 ? 를 붙여주면 저게 있어도되고 없어도되고 이런 말입니다.

테스트 한번 해볼까요?

괄호가 있을때 출력이 됩니다.

괄호가 없어도 출력이 됩니다.

다만 저 양식에서 더 벗어나면 출력이 되지 않겠죠. 앞에 엉뚱한 문자가 와있거나 전화번호가 총 10자리가 아니거나 하는 경우에는요.


* : 앞 항목이 없거나 여러번되는 것을 의미합니다.

ex) 대문자하나, 여러 대소문자와 공백을 포함하는 문자열의 정규 표현식.

[[:upper:]][[:upper:]][lower:] ]* 이 식을 짚어보자면 괄호를 잘보면됩니다. 첫번째가 대문자, 그뒤부터는 대소문자가 오는 모든 문자라고 해석해볼 수 있겠네요.

lower POSIX뒤에 띄어쓰기가 들어가있어야 꼭 띄어쓰기를 인식해 출력합니다. 아니면 모든 문자를 붙여써야해요.

그래서, echo "This works." | grep -E '[[:upper:]][[:upper:][:lower:] ]*\.'를 사용해보면,

둘 다 만족하고 있기때문에 정상적으로 출력이 되고있습니다

되지 않는다면 POSIX 뒤에 띄어쓰기가 꼭 있는지, POSIX는 제대로 썼는지, 또 저 정규식을 쓴다면 대문자로 시작이 되고있는지 확인해보시고 출력을 다시해보세요.

+ : 앞 항목이 한번이상되는 것을 의미합니다.

ex) 단일 스페이스로 구분된 하나 이상의 알파벳으로 구성된 그룹을 찾는 정규 표현식

^([[:alpha:]]+ ?)+$ 로 표현해볼 수 있겠는데요, 해석을 해보자면 모든 영소대문자를 포함해 공백이 포함되어있고 그 것이 하나 이상이다. 그리고 시작이나 끝에 포함된다. 뭐 이정도로 생각해볼 수 있습니다.

잘 출력이 되는군요. 므흣-

주의 할점은 영대소문자만 포함하는문자이기때문에 역시 숫자가 들어간다거나, 띄어쓰기가 한번만 포함되는 문자열이기때문에 두번 띄어쓰기를 했다거나 하는 경우에는 조건이 부합하기때문에 출력이 되지 않는 점을 유의해보시고, 여러가지 시도해보시길 바랍니다.


{} : 앞 항목이 지정 횟수만큼 반복되어있는 것을 의미합니다.

- {n} : 정확히 n번만 일치하는 선행 요소 검색

- {n,m} : 최소 n, 하지만 m번 이하로 일치하는 선행 요소 검색

- {n,} : n번 이상 일치하는 선행 요소 검색

- {,m} : m번 이하 일치하는 선행 요소 검색

이러한 내용으로 보아 응용을 해보자니 아까 해봤던 ^\(?[0-9][0-9][0-9]\)? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$ 이 기억나시나요?

계속 이렇게 써주던걸 {}를 이용하면 편리하게 표현할 수 있습니다.

^\([0-9]{3}\)? [0-9]{3}-[0-9]{4}이렇게요! 엄청 간편해졌죠?

그래서 사용해보면,

이렇게 짧은 식으로 표현이 편하게 가능하다는 것을 느낄 수 있습니다. 크..

또한 '070 123-4567'로 입력해도 정상적으로 출력이 됩니다.

대신에 이것도 자릿수 규칙을 벗어나 입력하나고하면 조건에 부합해 출력되지 않으니 참고하세요.



응용) phonelist.txt를 만들어봅시다.

cat > phonelist.txt

(123) 123-4567

(234) 123-1234

(01) 123-4567

(234) 12-1234

(345) 123-123 를 입력하고 Ctrl+C를 눌러 종료합니다.

grep을 해봅시다!

이번에는 반전매치 옵션까지 더해서 -Ev라는 옵션을 사용합니다.

grep -Ev '^\(?[0-9]{3}\)? [0-9]{3}-[0-9]{4}$' phonelist.txt

왜인지는 모르겠으나, 두자리수는 왜 갑자기 계속 출력되는건지 모르겠습니다.

수업시간에도 저렇게 한번 표시가 됐었는데 다음시간에 질문을해서 해답을 알아와야겠습니다.


오늘은 여기까지입니다!

반응형

댓글