Linux Command - awk

sycho·2024년 1월 1일
0

Linux Commands

목록 보기
19/30

awk

  • documentation

  • 명령어라기보단 프로그래밍 언어로 보는게 맞다. 특정 파일 내 특정 텍스트에 대한 조작에 특화되어 있다. 스크립트 언어다.

  • 개발에 참여한 Aho, Weinberger, Kernighan의 앞글자를 따서 만들어졌다.

  • 기본 틀은 위의 documentation에 있는 내용이지만, 실제 implementation은 여러가지가 존재한다. gawk, nawk, mawk가 대표적. 여기서는 대부분의 리눅스가 사용하는 gawk를 기준으로 설명한다. (GNU implementation)

Record, Field

  • awk에 대해 설명하기 전에 먼저 Record랑 Field에 대해서 설명을 해야 한다.

  • awk의 특정 명령어 실행은 record 단위로 이루어진다. record를 나누는 기준인 character인 record separator을 토대로 record 개수가 정해지며 기본값은 \n, 즉 입력값의 각 줄이 record라고 생각하면 된다. 이 값은 RS값을 바꾸는 형태로 수정이 가능하다.

  • 각 record는 또 field를 나누는 기준인 field seperator을 통해 또 나뉘어진다. 이것은 공백을 기준으로 나뉘어진다. \n, \t, 여러 공백도 하나의 공백으로 취급해서 나눈다.

  • 각 field는 $ 옆에 숫자로 접근이 가능하다. 첫번째 field는 $1, 두번째는 $2...의 형식. 전체 field 접근은 $0으로 가능하다. 마지막 field의 경우 $NF를 사용해서 접근하는 것도 된다.

  • 밑은 예시.

  PID TTY          TIME CMD
23576 pts/3    00:00:00 bash
23804 pts/3    00:00:00 ps
|---| |---|    |------| |--|
 $1    $2         $3     $4  ($NF) --> fields
|--------------------------| 
             $0                    --> record

awk 프로그램 기본 문법

  • awk program은 크게 규칙과 사용자정의 함수로 나뉘어진다.

  • 규칙은 pattern과 action으로 이루어져있다. action은 여러 줄이 가능하며 ;을 기준으로 나뉘어지고, {} 안에 전부 존재한다.

  • 규칙은 해당 pattern을 만족하는 모든 record들에 대해 중괄호 안의 action을 수행하라는 것을 의미한다. 만약 pattern만 지정되고 action은 지정되지 않았으면 해당 pattern을 만족하는 모든 record들을 출력하라는 의미가 된다. 만약 action은 지정되어 있고 pattern이 지정되지 않았으면 모든 record들에 대해 해당 action을 수행하라는 의미가 된다.

  • 타 언어처럼 조건문, 입출력 관련 문법, 그리고 그 외 예약어들이 존재를 한다. 대표적인건...

    • exit : 프로그램 즉시 종료
    • next : continue랑 유사하다. 현재 record에 대한 처리를 멈추고 다음 record의 처리 시작
    • print : 출력용 함수
    • printf : 출력용 함수. C언어의 printf와 유사.
  • 주석은 # 사이의 모든 내용물이 해당되며, \을 사용해 여러 줄을 하나의 줄로 나타내는게 가능하다.

실행

  • 짧은 프로그램의 경우에는 awk interpreter을 통해 바로 실행이 가능하다. 밑의 program부분에 awk program을 넣고 처리할 파일 (혹은 input stream)을 집어넣으면 된다. 이 때 프로그램 부분은 ''으로 둘려쌓여야 한다. 쉘에서 이를 shell용 프로그램으로 생각해서는 안되기 때문.
awk '(awk 프로그램)' (처리 대상 파일)
  • 반대로 파일에 담아야할만큼 큰 프로그램일 수도 있다. 이 경우 -f를 사용한다.
awk -f (awk 프로그램이 있는 파일) (처리 대상 파일)`

예시들

  • 예시 파일은 다음과 같다. 이름은 example1.txt. 2023 KBO 최종 순위 관련 통계다.
LG Twins 86 56 2 0.606 -
KT Wiz 79 62 3 0.560 6.5
SSG Landers 76 65 3 0.539 9.5
NC Dinos 75 67 2 0.528 11.0
Doosan Bears 74 68 2 0.521 12.0
KIA Tigers 73 69 2 0.514 13.0
Lotte Giants 68 76 0 0.472 19.0
Samsung Lions 61 82 1 0.427 25.5
Hanwha Eagles 58 80 6 0.420 26.0
Kiwwom Heroes 58 83 3 0.411 27.5
  • 앞에서 봤듯이 밑의 명령와 같이 하면 그냥 전부 출력을 한다.
$ awk '{print}' example1.txt
LG Twins 86 56 2 0.606 -
KT Wiz 79 62 3 0.560 6.5
SSG Landers 76 65 3 0.539 9.5
NC Dinos 75 67 2 0.528 11.0
Doosan Bears 74 68 2 0.521 12.0
KIA Tigers 73 69 2 0.514 13.0
Lotte Giants 68 76 0 0.472 19.0
Samsung Lions 61 82 1 0.427 25.5
Hanwha Eagles 58 80 6 0.420 26.0
Kiwwom Heroes 58 83 3 0.411 27.5
  • 여기서 승수만을 출력하고 싶으면 다음과 같이 하면 된다.
$ awk '{print $3}' example1.txt
86
79
76
75
74
73
68
61
58
58

Regex 활용

$ awk '/T/ {print $3}' example1.txt
86
79
73

Regex를 field 단위로 활용

  • 위의 방식은 record 단위로 regex pattern을 활용한다. field 단위로 활용을 하고 싶은 경우 어떤 field에 대해서 regex를 적용할건지를 먼저 지정, 이후 ~을 넣고 그 다음에 사용할 regex pattern을 집어넣으면 된다. 예를 들어 팀 이름의 두번째 부분에 t 혹은 T가 포함된 팀(기아, LG, 롯데)의 승수를 출력하고 싶으면 다음과 같이 한다.
$ awk '$2 ~ /[tT]/ {print $3}' example1.txt
86
73
68
  • 반대로 위에 해당되지 않는 팀들 (앞의 세 팀을 제외한 나머지)의 승수를 출력하고 싶으면 다음과 같이 부정형을 넣으면 된다.
$ awk '$2 !~ /[tT]/ {print $3}' example1.txt
79
76
75
74
61
58
58

대소관계 비교

  • 숫자, 문자열의 대소관계 비교가 가능하다. 밑은 승수가 75를 넘는 팀의 회사 이름을 출력한다.
$ awk '$3 > 75 {print $1}' example1.txt
LG
KT
SSG

range pattern

  • 밑의 구조를 가진다.
pattern1, pattern2
  • 생각보다 빡치는 문법이다.

    • 먼저 pattern1을 처음 만족하는 record를 시작 record로 취급한다.
    • 시작 record는 무조건 action을 수행한다.
    • 시작 record가 action 수행이 완료된 이후, 시작 record포함 매번 각 record마다 다음을 따진다
      • pattern 1을 만족하는가?
      • pattern 2를 만족하는가?
    • 만약 record가 pattern1만 만족하고 pattern2를 만족하지 않으면 다음 record의 action을 수행한다.
    • 만약 record가 pattern1을 만족하고 pattern2도 만족하며 다음 record가 pattern1을 만족하지 않으면 즉각 종료된다.
    • 만약 record가 pattern1을 만족하고 pattern2도 만족하며 다음 record도 pattern1을 만족하면 다음 record의 action을 수행한다.
    • 만약 record가 pattern1과 pattern2를 둘 다 만족하지 않으면 다음 record에 대해 action을 수행한다.
    • 만약 record가 pattern1을 만족하지 않고 pattern2는 만족하면 즉각 종료된다.
  • 예를 들어 LG를 포함하는 record부터 Doosan을 포함하는 record까지의 승수를 출력하고 싶으면 다음과 같이 하면 된다. 첫 줄이 pattern1 만족을 하면서 출력, 이때 pattern2가 만족이 안되서 다음 record가 출력이 되고, 한동안 pattern1, pattern2가 맞지 않아서 계속 다음줄도 출력되다가 Doosan을 만나면 즉각 종료된다.

$ awk '/LG/, /Doosan/ {print $3}' example1.txt
86
79
76
75
74
  • 앞의 규칙 체계로 인해 부등호를 사용시 많이 꼬일 수도 있다.
(1)
$ awk '$3 < 80, $3 >= 70 {print $1}' example1.txt
KT
SSG
NC
Doosan
KIA
Lotte
Samsung
Hanwha
Kiwwom

(2)
$ awk '$3 < 80, $3 < 70 {print $1}' example1.txt
KT
SSG
NC
Doosan
KIA
Lotte
Samsung
Hanwha
Kiwwom

(3)
$ awk '$3 < 70, $3 >= 70 {print $1}' example1.txt
Lotte
Samsung
Hanwha
Kiwwom

(4)
$ awk '$3 == 74, $3 <= 70 {print $1}' example1.txt
Doosan
KIA
Lotte

(5)
$ awk '$3 == 74, $3 >= 70 {print $1}' example1.txt
Doosan

(6)
$ awk '$3 == 74, $3 == 73 {print $1}' example1.txt
Doosan
KIA
  • 위에 대해 하나하나 설명하자면

    • (1) 및 (2)의 경우 승수가 80 미만인 KT가 먼저 출력된다. pattern1을 만족하기 때문. 문제는 이후의 모든 녀석들도 pattern1을 만족하기 때문에 pattern2의 만족 여부랑 상관없이 그냥 전부가 출력이 된다.
    • (3)의 경우 (1), (2)의 경우에서 pattern1조건이 달라진 경우에 해당된다.
    • (4)의 경우 롯데에서 멈추게 되는데, pattern1은 처음의 두산만 만족하며, 이후는 pattern1을 절대로 못만족해서 둘 다 못만족하는 KIA는 일단 출력, pattern2만 만족하는 첫 record인 롯데까지 action을 수행하고 종료가 되는 것이다.
    • (5)의 경우 두산만 출력된다. 이유는 두산이 pattern1과 2를 전부 만족하는데 KIA는 pattern1을 절대로 못만족하기 때문이다. 이 때문에 pattern2를 만족해도 즉각 종료된다.
    • (6)의 경우 정확히는 두산과 기아, 그리고 그 사이의 모든 팀들을 출력할 수 있게 된다.
  • 즉 range는 시작과 끝점을 확실하게 명시를 하는 형태의 특정 범위 안의 action 수행이라고 생각하면 된다. 간단히 말해, 6번처럼 쓰는 것만 올바르다고 봐야 한다. 그래서 pattern1,2에 부등호를 사용하는 것은 좋은게 아니라는 점 유의. 마치 python에서 range를 사용할 때의 시작점과 끝점에 범위를 집어넣는 느낌으로 프로그램을 만들려 하니 문제가 된다고 생각하면 된다.

BEGIN, END pattern

  • BEGIN : record를 본격적으로 처리하기 전에 수행하는 action들을 묶어넣는다.
  • END : record 처리가 완료된 후에 수행하는 action들을 묶어 넣는다.
$ awk 'BEGIN {print "scoreboard"}; {print $3}; END {print "end of scoreboard"}' e
xample1.txt
scoreboard
86
79
76
75
74
73
68
61
58
58
end of scoreboard

&&, ||

  • 참거짓 형태의 pattern을 섞는데 유용하다. 예를 들어 80 승수 미만, 70 승수 이상의 팀 통계 출력은 다음과 같이 한다.
$ awk '$3 < 80 && $3 >= 70 {print}' example1.txt
KT Wiz 79 62 3 0.560 6.5
SSG Landers 76 65 3 0.539 9.5
NC Dinos 75 67 2 0.528 11.0
Doosan Bears 74 68 2 0.521 12.0
KIA Tigers 73 69 2 0.514 13.0

예약된 변수들

  • NF : record 안의 field 개수 접근에 활용 가능

  • NR : record 개수 접근에 활용 가능

  • FILENAME : 처리 중은 입력 파일 이름

  • FS : field separator

  • RS : record separator

  • OFS : Output field separator. 출력물의 field를 어떻게 나눌지를 지정한다. 추후 보면 ,을 기준으로 여러 출력물을 특정 character을 사이에 두고 출력하는데, 그때 사이에 들어갈 character이 뭔지를 지정한다. 기본은 ' '.

  • ORS : Output record separator. print 이후에 줄바꿈을 해야 할 때 사용하는 character을 지칭한다. 기본은 /n. 출력물의 한 줄이 출력완료될때마다 이 값을 끝에 붙여넣는다.

  • file관련 변수들의 경우, 파일 처리 후에 제대로 값이 저장된다는 점 유의.

  • 밑은 입력 파일의 줄개수 세는 방법.

$ awk 'END {print "File", FILENAME, "contains", NR, "lines."}' example1.txt
File example1.txt contains 10 lines.

field/record separator 바꾸기

  • Field separator을 바꾸고 싶으면 다음과 같이 하자.
$ awk 'BEGIN { FS = "." } { print $1 }' example1.txt
LG Twins 86 56 2 0
KT Wiz 79 62 3 0
SSG Landers 76 65 3 0
NC Dinos 75 67 2 0
Doosan Bears 74 68 2 0
KIA Tigers 73 69 2 0
Lotte Giants 68 76 0 0
Samsung Lions 61 82 1 0
Hanwha Eagles 58 80 6 0
Kiwwom Heroes 58 83 3 0
  • 위는 -F로도 하는게 가능하다.
$ awk -F "." '{ print $1 }' example1.txt
LG Twins 86 56 2 0
KT Wiz 79 62 3 0
SSG Landers 76 65 3 0
NC Dinos 75 67 2 0
Doosan Bears 74 68 2 0
KIA Tigers 73 69 2 0
Lotte Giants 68 76 0 0
Samsung Lions 61 82 1 0
Hanwha Eagles 58 80 6 0
Kiwwom Heroes 58 83 3 0
  • Record separator을 바꾸고 싶으면 다음과 같이 하자.
$ awk 'BEGIN {RS = "." } { print $1 }' example1.txt
LG
606
560
5
539
5
528
0
521
0
514
0
472
0
427
5
420
0
411
5

action 활용

  • {} 안에 들어가며, ;을 기준으로 구별이 된다.

  • 변수 넣기, 산술, 증감, 조건문, 루프, 스위치, 출력용 함수들 등등이 가능하다. 어지간한 프로그램 언어 급의 표현능력을 가진다.

  • 몇가지 예시를 밑에서 들겠다. 먼저 여러개를 공백을 두고 출력하는것은 ,을 활용한다.

$ awk '{print $1, $5}' example1.txt
LG 2
KT 3
SSG 3
NC 2
Doosan 2
KIA 2
Lotte 0
Samsung 1
Hanwha 6
Kiwwom 3
  • 공백을 두지 않고 출력할거면 ,을 빼자.
$ awk '{print $1$5}' example1.txt
LG2
KT3
SSG3
NC2
Doosan2
KIA2
Lotte0
Samsung1
Hanwha6
Kiwwom3
  • 변수 말고 그냥 상수 문자열을 출력할거면 ""을 활용하자.
$ awk '{ print "first field:", $1}' example1.txt
first field: LG
first field: KT
first field: SSG
first field: NC
first field: Doosan
first field: KIA
first field: Lotte
first field: Samsung
first field: Hanwha
first field: Kiwwom
  • \n등도 다 활용이 가능하다.
$ awk 'BEGIN { print "one\ntwo\nthree"}' example1.txt
one
two
three
  • printf를 활용해 C의 printf와 유사한 형식으로 출력도 가능하다.
$ awk '{ printf "%3d. %s\n", NR, $0 }' example1.txt
  1. LG Twins 86 56 2 0.606 -
  2. KT Wiz 79 62 3 0.560 6.5
  3. SSG Landers 76 65 3 0.539 9.5
  4. NC Dinos 75 67 2 0.528 11.0
  5. Doosan Bears 74 68 2 0.521 12.0
  6. KIA Tigers 73 69 2 0.514 13.0
  7. Lotte Giants 68 76 0 0.472 19.0
  8. Samsung Lions 61 82 1 0.427 25.5
  9. Hanwha Eagles 58 80 6 0.420 26.0
 10. Kiwwom Heroes 58 83 3 0.411 27.5
  • 밑은 이번 리그에 있었던 무승부 개수를 계산한다.
 $ awk '{ sum += $5 } END { printf "number of draws : %d\n", sum/2 }' example1.txt
number of draws : 12

파일로 저장해서 실행 (-f)

  • 2가지 방법이 있다. awk command를 사용하거나 shebang을 이용해 그냥 일반 명령어처럼 사용하는 것이 가능하다.

  • 확장자는 .awk로 해야 한다.

  • 전자의 경우

$ cat examplepr.awk
BEGIN {
        i = 1;
        while (i < 6) {
                print "Square of", i, "is", i*i;
                ++i;
        }
}
$ awk -f examplepr.awk
Square of 1 is 1
Square of 2 is 4
Square of 3 is 9
Square of 4 is 16
Square of 5 is 25
  • 후자의 경우
$ cat examplepr.awk
#!/usr/bin/awk -f
BEGIN {
        i = 1;
        while (i < 6) {
                print "Square of", i, "is", i*i;
                ++i;
        }
}
$ ./examplepr.awk
Square of 1 is 1
Square of 2 is 4
Square of 3 is 9
Square of 4 is 16
Square of 5 is 25

awk에 쉘 변수 전달 (-v)

  • 쉘 스크립트에 awk를 활용하는 경우가 자주 있을 것이다. 이 때, 쉘의 변수를 awk에 전달해야 하는 경우도 분명 필요할 것이다. 여러 방법이 있으나 추천되는 방법은 해당 쉘 변수를 awk의 변수로 직접 지정을 하는 것이다. 이 때 -v를 활용한다.
$ cat example.sh
read num
awk -v n="$num" 'BEGIN {print n}'
$ ./example.sh
31
31
profile
CS 학부생, 핵심 관심 분야 : Embed/System/Architecture/SWE

0개의 댓글