grep

명령의 출력이나 파일에서 regexp 을 이용해 매칭 되는 라인, 스트링을 찾을 때 자주 사용되는 명령이 grep 입니다. grep 명령은 ed 라인 에디터를 만든 Ken Thompson 이 스트링 매칭 기능을 분리해서 별도의 명령으로 만든 것으로 grep 이라는 이름은 ed 명령의 g/re/p 에서 유래합니다. ( g 는 global search 를 re 는 regular expression, p 는 print 를 의미 )

이후에 extended regular expression 을 사용하는 egrep, 단순 fixed 스트링( regexp 가 아닌 )을 검색하는 fgrep 같은 명령이 추가되었지만 지금은 grep 하나의 명령에서 -E, -F 옵션을 통해 모두 제공됩니다. 하위 호환성을 위해 /bin/egrep, /bin/fgrep 명령이 아직 존재하지만 단순히 wrapper script 에 불과합니다.

grep 명령을 잘 사용하기 위해서는 먼저 regexp 에 대해서 알고 활용할 수 있어야 합니다. 여기서는 regexp 를 이용한 여러 가지 활용 방법보다는 명령에서 제공하는 전반적인 기능에 대해서 알아보겠습니다. 다음은 옵션 설정을 통해서 grep 명령에서 사용할 수 있는 regexp 종류입니다. ( 각각의 차이점에 대해서는 이곳 을 참고하세요 )

-G, --basic-regexp
              basic regular expression (BRE).  기본값 입니다.

-E, --extended-regexp
              extended regular expression (ERE).

-P, --perl-regexp
              Perl-compatible regular expression  (PCRE).

-F, --fixed-strings
              fixed  strings

매칭할 스트링에 regexp 문자가 포함될 경우 escape 해야 합니다

. 문자는 regexp 에서 사용되는 특수 문자로 임의의 한 문자에 해당됩니다. 따라서 . 문자 자체를 매칭하기 위해서는 escape 해야 합니다.

여러 개의 매칭 패턴을 사용

-e PATTERN, --regexp=PATTERN

-e 옵션을 사용하면 여러 개의 매칭 패턴을 적용할 수 있습니다.

$ grep -e 'harry' -e 'pulse' /etc/passwd
pulse:x:116:124:PulseAudio daemon,,,:/var/run/pulse:/bin/false
harry:x:1000:1000:NetworkManager:/home/harry:/bin/bash

또한 검색할 스트링이 - 문자로 시작할 경우 grep 명령의 옵션과 구분하기 위해서 사용할 수 있습니다.

# 검색할 스트링 '--width' 가 grep 명령의 옵션으로 인식돼 오류 발생
$ grep '--width' *.cpp
grep: unrecognized option '--width'
Usage: grep [OPTION]... PATTERN [FILE]...
Try 'grep --help' for more information.

$ grep -e '--width' *.cpp
OK
$ grep -- '--width' *.cpp
OK

대, 소문자 구분없이 매칭

-i, --ignore-case

대, 소문자 구분 없이 매칭하려면 이 옵션을 사용하면 됩니다.

invert 매칭

-v, --invert-match

invert 매칭은 반대로 매칭되지 않는 라인을 프린트 합니다.

Context line control

grep 명령을 사용하다 보면 매칭 되는 라인을 표시하는 것 외에 더해서 이전, 이후 n 라인을 표시하는 것이 필요할 때가 있습니다. 이때 사용할 수 있는 옵션입니다.

-A NUM, --after-context=NUM

-B NUM, --before-context=NUM

-C NUM, -NUM, --context=NUM

$ cat sample.txt
aaa
bbb
ccc
ddd
eee
fff
ggg

$ grep -A 2 'ddd' sample.txt     # After 2 lines
ddd   # ddd
eee
fff

$ grep -B 2 'ddd' sample.txt     # Before 2 lines
bbb
ccc
ddd   # ddd

$ grep -C 2 'ddd' sample.txt     # Context 2 lines
bbb
ccc
ddd   # ddd
eee
fff

$ grep -A2 -B3 'ddd' sample.txt
...

recursive 매칭

-r, --recursive

특정 디렉토리 이하 모든 파일을 대상으로 매칭합니다.

# ./dir1 ./dir2 디렉토리 이하 모든 파일에서 'hello' 매칭
$ grep -r 'hello' ./dir1 ./dir2

# 디렉토리 인수를 주지 않으면 현재 디렉토리가 적용됩니다.
$ grep -r 'hello'

-R, --dereference-recursive

디렉토리 심볼릭 링크가 있을 경우 -r 옵션은 기본적으로 follow 하지 않는데 반해 -R 옵션은 follow 합니다.
-r 옵션 사용시 특정 링크만 follow 하고 싶으면 해당 링크를 디렉토리 리스트에 같이 적어주면 됩니다.

include, exclude 설정

GLOBbing 패턴을 이용해서 특정 파일들만 포함시키거나 제외할 수 있습니다.

--include=GLOB

# 'hello' 스트링을 찾을 때 '*.c' 파일들만 검색
$ grep -r --include='*.c' 'hello'

--exclude=GLOB

# 'hello' 스트링을 찾을 때 '*.cpp' 파일을 제외하고 검색
$ grep -r --exclude='*.cpp' 'hello'

--exclude-dir=GLOB

# 'hello' 스트링을 찾을 때 '.git' 디렉토리를 제외하고 검색
$ grep -r --exclude-dir='.git' 'hello'

binary 파일은 제외하고 검색

바이너리 파일과 텍스트 파일이 혼합되어 있을 경우 이 옵션으로 바이너리 파일을 검색에서 제외할 수 있습니다.

-I

# 'hello' 스트링을 찾을 때 바이너리 파일은 제외하고 검색
$ grep -rI 'hello'

매칭 스트링만 프린트

-o, --only-matching

이 옵션은 매칭 라인을 프린트하는 것이 아니고, 매칭 되는 스트링만 프린트합니다.

$ sensors | grep -i core | grep -E -o '\+[0-9.]+'
+46.0
+86.0
+100.0
+46.0
+86.0
+100.0
+46.0
+86.0
+100.0
+46.0
+86.0
+100.0

directory, device 파일 skip 하기

-d ACTION, --directories=ACTION

-D ACTION, --devices=ACTION

입력으로 사용되는 파일명에 directory 나 device 이름이 포함될 경우 ACTION 값을 skip 으로 설정하면 skip 할 수 있습니다.

$ grep -I 'main' *
best.c:int main()
err.c:int main() {
grep: gd: Is a directory       <--- directory
gd
grep: gmp: Is a directory      <--- directory
gmp
main.c:int main() {

$ grep -I -d skip 'main' *     # directory skip
best.c:int main()
err.c:int main() {
main.c:int main() {

n 번째 매칭까지만 프린트

-m NUM, --max-count=NUM

전체 데이터를 검색할 필요 없이 n 번째 매칭에서 연산을 종료하고자 할 때 사용할 수 있습니다.

# 첫 번째 'cache 0' 스트링 매칭에서 이후 70 라인을 프린트하고 종료
$ cpuid | grep -m1 -A70 'cache 0'

매칭 라인 넘버 프린트

-n, --line-number

매칭 라인을 프린트할 때 해당 라인 넘버도 함께 프린트됩니다.

$ grep -n foo *.c
best.c:40:int foo()
err.c:12:int foo() {
main.c:9:int foo() {
...

출력에 파일명도 포함

-H, --with-filename

-h, --no-filename

grep foo *.c 와 같이 여러 개의 파일을 검색하는 경우는 -H 가 디폴트가 되어 파일명이 함께 표시되고 grep foo hello.c 와 같이 하나의 파일만 검색하는 경우 -h 가 디폴트가 되어 파일명이 표시되지 않습니다. 이것은 find 명령의 -exec 에서 grep 명령을 실행할때 하나의 파일로 인식되어 파일명이 표시되지 않는데 이때 -H 옵션을 사용하면 파일명을 함께 출력할 수 있습니다.

$ find * -name Makefile -exec grep -Hne 'include' {} \;

매칭 라인 카운트

-c, --count

해당 파일에 몇 개의 매칭 되는 라인이 있는지를 알려줍니다.

$ grep -c 'hello' *.html
aaa.html:2
bbb.html:0
ccc.html:1
...

단어 매칭

-w, --word-regexp

스트링의 일부분이 아닌 단어 매칭을 합니다.

파일명만 프린트

-l, --files-with-matches

-L, --files-without-match

이 두 옵션은 매칭 라인을 출력하지 않고 단지 파일명만 출력합니다.
-l 옵션은 매칭이 발견되는 파일명을 프린트하고 (첫 번째 매칭에서 스캐닝이 중단됩니다.)
-L 옵션은 반대로 매칭이 발견되지 않는 파일명을 프린트합니다.

# ./dir 디렉토리 이하 모든 파일을 검색해서 'hello' 를 포함하고 있을 경우 XXX 를 YYY 로 변경
$ grep -rl 'hello' ./dir | xargs sed -i 's/XXX/YYY/g'

라인 구분자가 NUL 문자일 경우

-z, --null-data

입력되는 데이터가 newline 대신에 NUL 문자를 라인 구분자로 할경우 사용합니다.

프린트 금지

-q, --quiet, --silent

아무 출력도 하지 않고 매칭이 발견되는 즉시 종료 상태 값으로 0 을 리턴하고 exit 합니다.

-s, --no-messages

오류가 발생해도 에러 메시지를 출력하지 않습니다.

이 옵션을 사용하면 명령 실행시 출력이 발생하지 않고 종료 상태 값만 설정됩니다. 따라서 스크립트를 작성할 때 if 문에서 사용하기 좋습니다.

$ AA="foo 12345 bar"

$ if echo "$AA" | grep -Eq '[0-9]+ bar'; then echo 111; else echo 222; fi
111

gzip 파일 grep

zgrep 명령을 이용하면 gzip 파일을 grep 할 수 있습니다.

$ zgrep –i error /var/log/syslog.2.gz

매칭에 escape sequence 를 사용하는 방법

grep 은 regexp 를 작성할 때 기본적으로 [[:blank:]] 같은 character class 만 사용할 수 있습니다.
따라서 매칭에 escape sequence 를 사용하려면 다음과 같은 방법을 이용해야 합니다.

# bash 의 경우 $' ' quotes 을 사용
bash$ echo -e 'hello\tworld' | grep $'\t'
hello   world

# sh 의 경우 "$(echo "\t")" 형식 을 사용
sh$ echo 'hello\tworld' | grep "$(echo "\t")"
hello   world

# 또는 PRE 를 사용
$ echo -e 'hello\tworld' | grep -P '\t'
hello   world

$ sudo ldconfig -v 2>/dev/null | grep -P '^\t'
/usr/lib/x86_64-linux-gnu/libfakeroot:
/lib/i386-linux-gnu:
/usr/lib/i386-linux-gnu:
. . .
. . .

grep 은 binary 파일도 매칭에 사용할 수 있습니다.

grep 이 binary 파일을 다루는 3 가지 방법

1. --binary-files=binary

디폴트로 binary 파일 내에 존재하는 스트링을 검색해서 매칭이 있을 경우 알려줍니다.
텍스트 파일에서처럼 매칭 라인을 출력하지는 않습니다.

$ grep ELF mycomm                # mycomm 은 실행 파일
Binary file mycomm matches  <--- 매칭이 있다고 알려줌
2. --binary-files=without-match

binary 파일일 경우 매칭을 시도하지 않고 skip 합니다.
이것은 -I 옵션과 같은 것입니다.

3. --binary-files=text

binary 파일도 text 파일처럼 취급하여 매칭하고 출력합니다.
이것은 -a 옵션과 같은 것입니다.

-a, --text
-b, --byte-offset

-b 옵션과 -o 옵션을 이용하면 파일내에 매칭되는 위치의 offset 값을 구할 수 있습니다.

# ':' 왼쪽이 offset 값
$ echo helloworld | grep -bo hel
0:hel

$ echo helloworld | grep -bo ll
2:ll

$ echo helloworld | grep -bo wor
5:wor

$ echo helloworld | grep -bo l
2:l
3:l
8:l

따라서 -abo 옵션을 이용하면 binary 파일내에서 매칭되는 위치의 offset 값을 구할 수 있습니다.
활용 예는 여기 를 참고 하세요

Quiz

다음 명령문은 어디가 잘못되었을까요?

$ cat /etc/mtab | grep cgroup

이것은 grep 명령을 사용하는 사람들 중에서 종종 발견되는 부분인데 명령문이 틀린 것은 아니지만 위 명령은 다음과 같이 할 수 있습니다.

$ grep cgroup /etc/mtab