Joswlv

awk 사용법

2016-10-23

awk

awk은 유닉스 기반 운영체제 (Linux, MacOs, Unix) shell programming에서 사용된다. 사용법이 간단하고 데이터 처리속도가 파이썬이나 R과 같은 인터프린터 언어와는 비교가 안될 정도로 빠르다.

C언어의 산술연산자(+,-,*,/,%,++,–,+=,-=,*=,/=,%=)와 조건문(for, while,do/while,if,and if/else)을 그대로 사용하기 때문에 ‘pseudo-C interpretor’라고 말하기도 한다.

awk은 행(column)지향적인 언어다. 즉 행렬로 구성된 데이터의 각 행을 원하는 방식으로 뽑아 손쉽게 데이터 처리를 한다. 그리고 그결과를 standard input 으로 보낸다. 사용법이 매우 간단하고 처리속도가 빠르기 때문에 대용량의 데이터를 다루는 분야에서 매우 유용하게 사용딘다.

사용 법은 다음과 같다.

awk 'pattern {action}' file

형식은 아주 간단하다. awk이 실앻되면 file을 앞에서부터 읽기 시작해서 정의된 pattern을 만족시키는 열에 대해서 {}안에 정의된 action을 실행한다.

사용 예

# awk 의 초간단 사용법: awk 'pattern { action }' file
# awk 는 각 열을 한줄씩 읽어들이면서 pattern 을 만족시키는 {action} 을 실행합니다.
# awk 은 각 열을 space 를 기준으로 끊어 따로 저장하는데 예를 들면, 읽고 있는 열이
# 'John apple 3' 인 경우 John 은 $1 이란 변수에, apple 은 $2 란 변수에 집어넣습니다.
 
awk '$1==0 { print $2 }' file  # 읽고 있는 열의 첫번째 값($1)이 0이면 둘째 값($2) 출력
awk < file '$1==0 { print $2 }'  # 이것도 위와 같은 결과를 출력합니다
 
awk < file '/^test/ { print $2 }' # regular expression 도 사용 가능합니다. 
 
# pattern 부분은 옵션입니다. 필요하지 않으면 빈칸으로 두면 됩니다.
awk < file '{ print $2, $3 }' 
 
# 행을 표시하지 않으면 전체를 출력합니다. $0 은 열 전체를 의미합니다. 즉 둘다 결과는 같습니다. 
awk < file '{print}' 
awk < file '{print $0}'

이때 pattern은 옵션이다. pattern란에 정규표현식이 들어가도 된다. 또 다른 사용법은 파일로 만든후에 파일을 실행시키는 법이다. 명령어가 길 경우 유용하다.

# awk 의 또다른 사용법: awk -f filename
awk -f hello.awk
 
--- hello.awk file ---
#!/bin/awk -f
BEGIN { print "File\tOwner" }
{ print $8, "\t", $3}
END { print " - DONE -" }
# awk 은 space 와 tab 을 기준으로 행을 구분합니다. 하지만 -F 써서 이를 바꿀 수 있습니다.
# awk 은 $1, $2.. 등을 field 라고 부르는데 그래서 옵션이 -F 즉 field separator 입니다. 
# 참고할 점은 일단 seperator 를 바꾸면 space 와 tab 은 무시합니다.
 
awk < /etc/passwd -F: '{ print $5 }' # ':' 를 기준으로 끊어 다섯번째 값을 출력합니다
 
# awk 을 이용한 연산
echo 8 4 | awk '{ print ($1-32)*(5/9) }'
> -13.3333  # awk 는 숫자를 실수로 다룹니다 (즉, 소수점까지 계산합니다)
 
# awk 의 다양한 처리방식
echo 5 4 | awk '{ print $1 $2 }' # $1$2 이렇게 붙여도 결과는 같습니다
> 54
echo 5 4 | awk '{ print $1,$2 }'
> 5 4
echo 5 4 | awk '{ print $1+$2 }'
> 9
echo a z | awk '{ print $1+$2 }' # 문자를 산술 연산자로 더하면?
> 0

awk에서 변수를 사용해 보다 정교한 프로그램을 작성할 수 있다. 우선 awk가 자동으로 생성하는 시스템 변수부터 시작하면 가장 흔하게 쓰는 시스템 변수는 위의 예에서 본 $1, $2등 이다. 각 열을 읽으면서 $1,$2등에 첫번째 행, 두번째 행에 해당하는 값을 자동으로 집어 넣는다.

# 각 열의 평균값을 구해봅시다. NF 는 Number of field 의 약자로 읽어들이는
# 행의 갯수를 담는 고정 변수입니다. 
# 매열마다 따로 계산해야 하므로 사용자 변수 total=0 으로 초기화 시킵니다.
# ; 를 이용하여 여러 명령어를 순차적으로 나열할 수 있습니다. 

awk '{ total=0; for (i=1; i<=NF; i++) total += $i; print total/NF; }'

# 'BEGIN {} pattern {action} END {}' 구문을 이용하여 
# 처음과 끝에서만 작동하는 명령어를 삽입할 수 있습니다.
awk '{ tot += $1; n += 1; }  END { print tot/n; }'

# awk built-in variables(대표적인 awk 고정 명령어들) 
NF: Number of fields in the current record (현재 읽고 있는 열의 행의 갯수) 
NR: Number of record processed since the beginning of current awk execution
(지금까지 읽어들인 열의 갯수) 
OFS: Output field separator (출력하는 행 구분자). Default value = " " 
ORS: Output record separator (출력하는 열 구분자). Default value = "\n"

printf라는 함수를 보자. 지금까지는 awk의 기본 출력함수인 print만 보았다. 하지만 awk은 C의 printf함수도 지원한다. 이 함수를 이용하면 awk으로 처리한 데이터를 보다 정교하고 다양하게 출력할 수 있다.

# 첫번째 행만 제거하고 나머지는 모두 출력합니다.
awk '{ for (i=2; i<=NF; i++) printf "%s ", $i; printf "\n"; }'
 
# 주의사항: print vs. printf
print 함수는 출력후 자동으로 그리고 내부적으로 '\n' 을 붙입니다. 즉 따로 '\n' 을 출력하지 않아도 next line 으로 넘어갑니다. 하지만 printf 는 반드시 '\n' 을 따로 출력해야 합니다. 바꿔말하면 '\n' 을 출력하지 않으면 전체를 모두 이어서 하나의 열로 만들 수 있습니다.

추가적으로 awk의 내장형 함수들이 존재한다.

length(string): 문자열의 문자수를 리턴합니다. 괄호를 비우면 현재 열의 전체 문자수를 리턴.	 
int(number): 숫자의 정수부분만 떼어 리턴합니다.
split(string, array, separator): 문자열을 separator 를 기준으로 분할하여 array 에 넣습니다.
그리고 array 의 index 를 리턴합니다. array[1].. 이렇게 사용하면 됩니다.	 
sprintf(format, arguments) 포맷에 맞추어 변수들이 값을 출력합니다.
substr(string, position, length) 문자열의 특정 위치에서 특정 길이만큼 뽑아 리턴합니다.

이것들 중에서 split 함수의 예를 보면 다음과 같다.

# 만약 아래와 같은 데이터가 있다고 합시다
chr8:28481001-28483000     NA   0   2   3   0
chr6:158956601-158957200   NA   0   2   2   2
chr20:42185201-42188600    NA   0   0   0   3
chr7:107542001-107543800   NA   0   0   2   0
chr14:24525801-24529600    NA   0   3   0   2
chr6:130689201-130690000   NA   2   0   0   2

# 첫번째 field 를 쪼개서 DNA 의 길이를 계산하여 아래와 같이 출력하려고 합니다
chr8  2000  0 2 3 0
chr6  600   0 2 2 2
chr20 3400  0 0 0 3
chr7  1800  0 0 2 0
chr14 3800  0 3 0 2
chr6  800   2 0 0 2

# 간단히 설명하면,
# 1. 먼저 1$ 를 : 와 - 를 기준으로 쪼개서 a 라는 array 에 넣습니다: split($1,a,":|-")
# 2. 그리고 염색체와 DNA 길이를 프린트 합니다: printf "%s %s",a[1],a[3]-a[2]+1
# 3. $1,$2 제외한 후 나머지를 프린트 합니다: $1=$2="";print
# 4. 모든 action 을 ; 을 통해 연결합니다 (이는 각각의 action 을 순차적으로 연산하도록 합니다)

awk '{split($1,a,":|-");printf "%s %s",a[1],a[3]-a[2]+1;$1=$2="";print}'

# '|' 은 OR 연산자 입니다. ':' 와 '-' 둘중에 하나를 만나면 쪼개도록 합니다.
# 중간에 printf 를 사용한 이유는 print 와는 다르게 '\n' 가 포함되어 있지 않아 뒤의 값들을 
# 이어서 출력하도록 합니다. 마지막에 사용된 print 는 '\n' 이 포함되어 있으므로 
# 자연스럽게 다음 줄로 내려가게 합니다.
#특정파일을 탭 또는 공백으로 구분되어 있는데, 2번째 부분만 출력하고 싶다.

awk '{print $2}' file
cat file | awk '{print $2}'

#특정파일이 ','로 구분되어 있는데 2번째 칼럼만 출력하고 싶다.

awk -F "," '{print $2}' file

#파일의 특정 칼럼에 특정값이 있는 갯수를 알고 싶다.

awk 'BEGIN{sum=0}{if ($2=="TEXT"){sum+=1}} END{print sum}' file
awk "{print FILENAME}" test.txt 
= test.txt 파일의 레코드 개수만큼 파일이름을 출력한다. 

awk "{print NR}" test.txt
= test.txt 파일의 레코드 번호를 출력한다. 

awk 'BEGIN {FS="\t"} {print $1 ,  $2}' test.txt
= test.txt 파일의 필드 구분자를 "\t" 으로 지정하고, 1번째와 2번째 필드를 프린트 한다. 

awk 'BEGIN {FS="\t"; OFS ="-"} {print $1 ,  $2} END {print "총 레코드의 수 : " NR}' test.txt
= test.txt 파일의 필드 구분자를 "\t" 로 지정하고, 1번째와 2번째 필드를 프린트 하되, 필드 구분자를 "-"
로 바꾸어 출력하고, 모든 레코드가 끝난 뒤, 총 레코드의 수를 출력한다. 

정리

awk [-f 프로그램파일] [-F 필드구분자] [“패턴{액션}”] [처리할 파일명]

옵션:

-f: 프로그램파일(패턴을 저장한파일)

-F: 필드 구분자

“패턴{액션}”

awk 시스템 변수

변수명 내용
FILENAME 현재 처리중인 파일명
FS 필드 구분자로 디폴트는 공백
RS 레코드 구분자로 그폴트는 새로운 라인
NF 현재 레코드의 필드 개수
NR 현재 레크드의 번호
OFS 출력할 때 사용하는 FS
ORS 출력할 때 사용하는 RS
$0 입력 레코드의 전체
$n 입력 레코드의 n번째 필드

자주 사용하는 예

[irteam@exexex ~]$ cat t_06.txt
863328267  2589984801  /log/applog/2017/03/06
1356084140  4068252420  /log/logsss/2017/03/06
6829747498  20489242494  /log/dmp_log/2017/03/06
18849  56547  /log/logs/2017/03/06
2867572830  8602718490  /log/join/2017/03/06
1224  3672  /log/dsapplog/2017/03/06

이런 내용에서 첫번째 column 의 합을 구하고 싶을 때

awk '{sum += $1} END {print "sum = ",sum}'

이렇게 적으면 된다.

예>

awk '/west/' datafile : west 라는 글이 있는 줄 출력

awk '/^north/' datafile : north로 시작하는 줄 출력

awk '/^(no | so)/' datafile : no 또는 so 로 시작하는 줄 출력

awk '{ print $3, $2 }' datafile : datafile 리스트의 세 번째 와 두 번째 필드를 스페이스로 띄어서 출력

awk '{ print $3 $2 }' datafile : datafile 리스트의 세 번째 와 두 번째 필드를 그냥 붙여서 출력

awk '{ print "Number of fields : " NF} ' datafile : datafile의 각 줄마다의 필드수를 리턴한다.

awk '$5 ~ /\.[7-9]+/' datafile : 다섯 번째 필드가 마침표 다음엣 7과 9사이 숫자가 하나 이상 나오는 레코드 출력

awk '$2 !~ /E/ { print $1, $2 }' datafile : 두 번째 필드에 E 패턴이 없는 레코드의 첫 번째와 두 번째 필드 출력

awk '$3 ~ /^Joel/{ print $3 " is a nice guy."} ' datafile : 세 번째 필드가 Joel로 시작하면 “ is a nice guy”와 함께 출력

awk '$8 ~ /[0-9][0-9]$/ { print $8 }' datafile : 여덟 번째 필드가 두 개의 숫자이면 그 필드가 출력

awk '$4 ~ /Chin$/ { print "The price is $" $8 "." }' datafile : 네 번째 필드가 Chine으로 끝나면 “The price is $” 8번 필드 및 마침표가 출력

awk -F: '{ print $1 } ' datafile : -F 옵션은 입력 필드를 ‘:’로 구별.

awk -F"[ :]" '{ print $1, $2 } ' datafile : 입력 필드로 스페이스와 ‘:’를 필드 구별자로 사용

awk -f awk_script.file datafile : -f 옵션은 awk 스크립트 파일 사용할 때 씀.

awk '$7 == 5' datafile : 7번 필드가 5와 같다면 출력

awk '$2 == "CT" { print $1, $2 }' datafile : 2번 필드가 “CT” 문자와 같으면 1, 2 번 필드 출력

awk '$7 < 5 { print $4, $7}' datafile : 7번 필드가 5보다 작다면 4번, 7번 필드 출력

awk '$6 > .9 { print $1, $6}' datafile : 6번 필드가 .9 보다 크다면 1번, 6번 출력

awk '$8 > 10 && $8 < 17 ' datafile

awk '$2 == "NW" || $1 ~ /south/ { print $1, $2 }' datafile


Reference


next Optional

Comments