2) 리눅스 명령어 grep, 원하는 라인만 출력하기

리눅스에서 grep은 대단히 유용하면서 자주 쓰이는 명령어 중에 하나이다.
본 포스팅에서는 수십~수백만 라인 중에서 사용자가 원하는 라인만 골라서 출력해주는 grep 명령어의 사용법에 대해서 알아본다.

수십~수백 줄의 문자열로 가득한 파일에서 내가 원하는 라인만 출력하는 것은 인력으로 어느 정도 가능한 일이지만 천 줄이 넘어가면서부터는 사람의 힘으로는 도저히 불가능한 작업이 된다. 여기서 ‘내가 원하는 라인‘이란 쉽게는 ‘내가 찾는 문자 또는 문자열이 들어있는 라인‘, 좀 더 복잡하게는 ‘내가 찾는 패턴이 들어있는 라인‘으로 정의할 수 있다.

내가 찾는 문자열이 들어간 라인 출력

그럼, grep 명령어를 가장 쉽게 사용하는 방법인 문자열 검색부터 소개해본다.

grep 명령어를 배우기 전 효과적인 grep을 쓰기 위한 기초 명령어를 알아본다.
리눅스 명령어 중 ‘ps’는 현재 실행되고 있는 프로세스의 상태를 출력한다. 터미널에 ps 라고만 입력 후 엔터를 치면 보통 대부분 다음 화면같이 나올 것이다.

[root@kaka]ps 
PID    TTY     TIME     CMD
13480  pts/0   00:00:00 bash 
13851  pts/0   00:00:00 ps

현재 터미널 쉘이 bash가 아니라면 다르게 나올 수도 있지만 큰 차이는 없을 것이다.
ps 명령어에는 보통 aux 옵션을 많이 사용한다. 각 옵션을 소개하자면,
a: 모든 사용자 프로세스 출력 옵션
u: 자세한 정보 출력
x: 제어터미널이 없는 프로세스도 출력
(정보 출처: http://finetia.egloos.com/1514615)

터미널에 ps aux라고 친 후 엔터를 누르면 수십, 수백 개의 프로세스 설명 리스트가 출력될 것이다.
문제는 여기서 발생한다.
수백 개의 리스트 중에서 내가 원하는 프로세스 설명만 출력할 순 없을까?
특정 프로세스 정보를 알아야 하는 경우, 이를테면 PID를 알아내서 강제종료해야 할 경우가 생길 수 있다. 이때 수백 줄이 넘는 프로세스 리스트를 일일이 눈으로 찾는 것은 너무 고달픈 일일 것이다.
여기서 바로 grep 명령어를 쓰면 모든 것이 해결된다.

만약 프로세스 정보 리스트(ps aux) 중에서 톰캣 프로세스를 찾고 싶을 경우는 다음과 같이 명령어를 사용하면 된다.

[root@kaka] ps aux | grep "tomcat"
root  14099  0.0  0.0  65344  840  pts/0  S+  18:48  0:00  grep tomcat
root  27472  0.6 54.6  176558 460448  ?   Sl  Nov29  19:36  /usr/local/java...(생략)

1: ps aux 명령 후 출력 리스트를 파이프 라인을 통해서 grep 명령어에 전달한다. grep 명령어에서는 input으로 들어오는 모든 라인 중에서 쌍따옴표 안에 있는 “tomcat” 이라는 문자열이 들어있는 라인을 출력한다.
2: ps aux 명령어는 실행 중인 모든 프로세스를 출력하기 때문에 1줄의 grep “tomcat” 조차도 출력한다. 따라서 1줄의 grep “tomcat” 명령어도 grep에 의해서 검색된다.
3: 우리가 찾고자 했던 프로세스 정보이다. PID, 실행시간, 실행 위치 등의 정보를 얻을 수 있다.

ps aux | grep “프로세스 관련 문자열” 조합은 실제 작업 중에서 빈번하게 사용된다. 특히 특정 프로세스를 찾아서 강제종료하고 싶은 경우가 많이 발생하는데 이때 유용하게 사용이 가능하다.

내가 찾는 패턴이 들어있는 라인 출력

단순 문자열 검색에 비해 좀 더 강력한 방법이다.

내가 찾는 패턴‘의 예를 들어보면 다음과 같다.
Christmas
X-Mas
Mascherano
위 세 라인이 test.txt라는 파일에 저장되어 있다고 했을 때,
test.txt 파일에서 크리스마스와 관련된 라인만 출력하고 싶은 경우가 생겼다. ‘Christmas’, ‘X-Mas’가 그 대상이다.
두 문자열 모두 ‘mas’가 들어가지만 1번째 줄의 mas는 전부 소문자이지만, 2번째 줄의 Mas는 ‘M’이 대문자이다.
따라서 grep "mas" test.txt로 치면 1번째 줄만 검색되고, grep "Mas" test.txt로 치면 2,3번째 줄이 출력된다.
명확히 출력하고자 하는 라인은 1,2번째 줄이기 때문에 단순 문자열 검색만으로는 가능하지 않다.

지금 첫 번째 문제는 1번째 줄과 2번째 줄의 ‘mas-Mas’의 대소문자 구분이고 두 번째 문제는 2번째 줄과 3번째 줄이 동일하게 ‘Mas’를 포함하고 있다는 것이다.

첫 번째 문제는 grep의 ‘-i’ 옵션을 사용하면 된다. -i 옵션은 대소문자를 구분하지 않고 검색한다는 의미이다.

[root@kaka] grep -i "mas" test.txt
Christmas
X-Mas
Mascherano

위와 같이 -i 옵션을 사용할 경우 ‘mas’와 ‘Mas’를 구분하지 않고 동일하게 취급한다. 뿐만 아니라 ‘MAS’, ‘mAs’, ‘MAs’와 같은 형태도 모두 검색된다.

첫 번째 문제를 해결했으니 두 번째 문제로 넘어가보자.
1,2번째 줄과 3번째 줄의 차이점은 ‘Mas’가 가장 뒤에 오느냐, 아니면 앞에 오느냐이다. 1,2번째 줄은 문자열의 맨 마지막에 오지만 3번째 줄은 가장 앞에 ‘Mas’가 위치한다.
이를 패턴화시켜 grep 에 사용해 볼 수 있다.

여기서 정규표현식이란 개념이 등장한다. 정규표현식에서 가장 중요한 것이 바로 ‘패턴’, ‘규칙’을 찾는 것이다. 정확한 규칙을 찾아 정규표현식의 문법에 맞게 작성만 하면 그 어떤 문자열도 찾아낼 수 있다.

정규표현식에서 모든 문자를 표현하는 게 바로 ‘.'(온점)이다. 여기서 주의할 점은 문자열이 아닌 문자라는 것이다. 이를 위 세 개 example에 적용해보자

1번째 줄의 Christmas에서 ‘tmas’는 ‘.mas’로 패턴화시킬 수 있다.
2번째 줄의 X-Mas에서 ‘-Mas’역시 ‘.mas’로 패턴화시킬 수 있다.
하지만 3번째 줄의 Mascherano에서는 ‘.mas’로 패턴화시킬 수 없다. ‘Mas’가 가장 앞에 오기 때문이다.

이를 이용하여 grep 명령어에 사용해보면 다음과 같다.

[root@kaka] grep -i -e ".mas" test.txt
Christmas
X-Mas

1: grep 옵션 중 -e 옵션은 정규표현식을 사용한다는 의미이다. -i(대소문자 구분x), -e(정규표현식 사용) 옵션이 사용된 것이다.
2~3: 처음에 의도했던 결과대로 출력이 된 것을 확인할 수 있다.

정규표현식은 grep 명령어 뿐만 아니라 awk, find 명령어 에서도 빈번하게 사용된다. 또한 리눅스 명령어 외에도 다른 다양한 프로그래밍 언어에서 지원하고 있기 때문에 사용법만 익힌다면 획기적인 생산성 향상을 이뤄낼 수 있다. 정규표현식을 쓰지 않고 특정 패턴을 포함하는 문자열을 찾는 것은 가능하긴 하지만 확실히 비효율적인 부분이 많게 된다.

grep 명령어의 두 가지 옵션을 알아봤는데 이외에도 많은 다양한 옵션이 있으니 찾아보면 좋을 것이다.
-i, -e와 많이 쓰이는 것이 -v 옵션인데 이는 쌍따옴표 내부의 문자열 또는 문자열 패턴을 포함하지 않는 라인만 출력한다.
-v 옵션을 사용하여 우리가 원하는 라인(1,2)만 출력하고자 한다면 다음과 같이 할 수 있다.

[root@kaka] grep -v -i -e "mas." test.txt
Christmas
X-Mas

1: grep -i -e “mas.” text.txt의 결과 값이 Mascherano이기 때문에 -v 옵션을 주면 Mascherano를 제외한 다른 라인이 출력된다.
2~3: 처음에 의도했던 결과대로 출력이 된 것을 확인할 수 있다.

1) 리눅스 명령어 awk, 원하는 항목만 추출하여 더하기

리눅스 명령어 중 awk를 사용하여 여러 줄의 문자열 중 각 라인에서 원하는 부분만 추출하여 더하는 간단한 스크립트를 작성한다.
예를 들어 다음과 같은 문자열 파일이 있을 때(각 항목은 버티컬 바(‘|’)로 구분되어 있다, 파일 이름은 ‘sales.txt’)
apple|1|3000
banana|2|2400
grape|4|3500

첫 번째 항목은 품목명을 나타내고 두 번째 항목은 판매 개수, 세 번째 품목은 판매 단가를 나타낸다.
여기서 총 판매액을 구하고 싶을 때, 각 라인의 판매 개수 * 판매 단가를 모두 더하면 되는데 이를 awk 라는 명령어를 사용하면 편하게 구현할 수 있다.

먼저 세 가지 항목 중 첫 번째는 제외하고 나머지 두 번째와 세 번째 항목을 추출해야 한다.

awk '{ \
      split($0, split_line, "|");  \
      print split_line[2];  \
      print spilt_line[3]; \
}' sales.txt

awk 명령어를 사용하는 방법은 크게 두 가지가 있는데 첫 번째는 위와 같이 awk 명령어 기술 후 awk 문을 바로 이어서 붙이는 것이고
다른 하나는 awk문을 파일에 저장한 후 ‘awk -f awk파일명’ 과 같은 방식으로 사용하는 것이다.
awk문이 길어질 경우 명령행에 적는 것이 복잡해지기 때문에 awk문 파일로 관리하는 것이 더 합리적이다.

이제 위 코드 설명에 들어가자면,
awk문은 ‘{ }’ 사이에 기술한다. 위 코드에서는 2~4번째 줄이 awk문이 되는 것이다.
2: split 함수를 사용한다. awk는 다양한 내장함수를 사용할 수 있는데, 구분자를 기준으로 문자열을 자르고 싶을 때 사용하는 함수가 바로 split이다.
split 함수의 첫 번째 인자는 자르고자 하는 대상 문자열이며, 위 코드에서는 ‘$0’이다. awk에서 $0은 라인 전체를 의미한다.
split 함수의 두 번째 인자는 대상 문자열을 자르고 난 후 저장할 배열 변수이다. 위 코드에서는 split_line이라는 변수에 저장시킨다.
세 번째 인자는 구분자를 지정한다. sales.txt 파일에서 각 라인의 구분자는 버티컬 바(‘|’)이기 때문에 구분자를 “|”으로 지정해준다.

3: 구분자로 잘라진 문자열들이 저장된 배열 변수 split_line의 두 번째 인덱스를 출력한다.
다른 프로그래밍 언어의 배열과 달리 첫 번째 인덱스가 1부터 시작하기 때문에 split_line 변수에는 1:apple, 2:1, 3:3000 과 같은 식으로 저장된다.
두 번째 인덱스를 출력했기 때문에 sales.txt 파일의 각 라인의 두 번째 항목인 판매 개수가 출력된다.

4: 세 번째 인덱스를 출력한다.
세 번째 인덱스는 sales.txt 파일의 각 라인의 세 번째 항목인 판매 단가가 출력된다.

우리는 판매 개수와 판매 단가를 곱한 후 모두 더한 값을 구하는 것이 최종 목표이기 때문에 위 코드로는 구할 수 없다.
위 코드를 고쳐서 판매 개수와 판매 금액을 곱한 후 출력해보자

awk '{ \
      split($0, split_line, "|");  \
      line_sum = split_line[2] * spilt_line[3];  \
      print line_sum; \
}' sales.txt

2: 판매 개수(split_line[2])와 판매 금액(split_line[3])을 곱한 후 line_sum 변수에 저장한다.
3: line_sum 변수를 출력한다.

위 코드를 실행시키면 다음과 같은 출력값을 확인할 수 있다.

3000
4800
14000

이제 세 개의 값을 더한 최종 판매 금액만 구하면 된다.

awk 'BEGIN{ \
    total_sum=0; \
}{ \
    split($0, split_line, "|");  \
    total_sum += split_line[2] * split_line[3]; \
}END{ \
    print total_sum; \
}' sales.txt

BEGIN과 END라는 키워드가 눈에 띌 것이다. awk문 내부는 해당 파일의 각 라인을 읽으면서 수행된다. sales.txt는 총 라인수가 3이기 때문에 awk ‘{}’ 내부는 세 번 실행되는 것이다. 그런데 이번 예제와 같이 각 라인에 영향을 받지 않고 파일 전체에서 관리하고 싶은 코드가 있을 경우에는 BEGIN과 END를 사용한다. 키워드의 의미 그대로 파일을 처음 읽어들일 때 BEGIN{ } 내부가 실행되고 파일을 전부 읽어들인 후 END{ } 내부가 실행된다.
2: BEGIN 내부에 total_sum 이라는 변수를 선언하며 0을 대입하고 있다.
5: 2번째 줄에서 선언한 total_sum 변수에 판매 개수 * 판매 단가 값을 누적 합산해주고 있다. ( += )
7: 마지막으로 프로그램 종료 전, total_sum을 출력한다.

위 코드를 실행하면 다음과 같은 출력값을 확인할 수 있다.

21800

따라서 총 판매 금액은 21,800이 되는 것이다.
동일한 기능을 수행하는 프로그램을 파이썬이나 자바로도 충분히 작성할 수 있지만, 이런 간단한 스크립트 연산만으로도 원하는 값을 구할 수 있다면 굳이 파이썬, 자바 코드까지 사용할 필요는 없다는 게 내 생각이다.

awk는 사용자의 능력에 따라서 무궁무진하게 이용될 수 있는 명령어이다. 위에서 소개된 것들은 awk의 전체 기능 중에서도 가장 간단한 것들만 사용한 것으로
나머지 awk 기능을 자유자재로 사용할 수 있다면 그것만으로도 awk라는 또다른 강력한 언어 스킬을 보유하게 되는 것이다.