$0 = $0, $1 = $1

레코드 값을 다시 레코드 변수에 대입하고, 필드 값을 다시 필드 변수에 대입하는 것은 아무 의미가 없어 보이는데요. 이것은 awk 에서 필드가 분리되어 필드값이 설정되고 레코드 값이 설정되는 내부 작동 방식에 관계된 내용입니다. 스크립트를 오류 없이 작성하기 위해서 알고 있어야 할 필요가 있습니다. 타이틀은 $0=$0, $1=$1 로 작성하였지만 꼭 $0, $1 값을 대입에 사용해야 되는 것은 아니고 어떤 값이든 $0 나 $1, $2, $3 ... 변수에 대입 연산을 할 때 적용되는 개념입니다. ( ++ -- 연산도 포함 )

$0 = $0 는 FS 에따라 FIELDS ( $1, $2, $3 ... NF ) 재설정 !
$1 = $1 는 OFS 에따라 RECORD ( $0 ) 재설정 !

먼저 스크립트 내에서 FS 값을 변경했을 경우 적용되는 시점에 대해 알아보겠습니다.

$ cat file
aa=11,bb=22,cc=33
dd=44,ee=55,ff=66
gg=77,hh=88,ii=99
-----------------

$ awk '{ printf "(%s) (%s) (%s)\n", $1,$2,$3 }' FS=, file
(aa=11) (bb=22) (cc=33)
(dd=44) (ee=55) (ff=66)
(gg=77) (hh=88) (ii=99)

# 코드 실행 중에 FS="=" 로 변경
$ awk '{ printf "(%s) (%s) (%s)\n", $1,$2,$3 }
         NR==1 { FS="=" 
                 printf "(%s) (%s) (%s)\n", $1,$2,$3 }
' FS=, file
(aa=11) (bb=22) (cc=33)
(aa=11) (bb=22) (cc=33)   # FS="=" 로 변경했지만 필드값이 변경되지 않는다.
(dd) (44,ee) (55,ff)      # 다음 레코드 입력부터 FS="=" 값이 적용된다.
(gg) (77,hh) (88,ii)

$0 변수에 대입 연산

위 예제에서 코드 실행 중에 FS="=" 로 변경하였지만 이어지는 프린트문에서는 필드값이 변경되지 않고 이전과 동일한 값이 표시되는 것을 볼 수 있습니다.

레코드에서 필드가 분리되어 필드 변수가 ( $1, $2, $3 ... NF ) 설정되는 시점은 레코드 값이 $0 변수에 대입될 때입니다. 그러므로 명령 사이클을 시작하기 위해 입력 스트림으로부터 레코드를 읽어들여 $0 변수에 대입할 때나, 또는 gsub(/foo/,"bar") 같은 스트링 함수를 실행하여 결과가 $0 에 대입되는 경우 모두에 해당됩니다. 따라서 다음과 같이 코드 실행 중에 FS 값을 변경한 후에 $0=$0 를 해주게 되면 새로운 FS 값에 따라 다시 필드가 분리되어 변수값이 설정됩니다.

# 처음 FS 값은 ','
$ echo 'aa=11,bb=22,cc=33' | 
awk '{ printf "(%s) (%s) (%s)\n", $1,$2,$3 
       FS="="; $0=$0
       printf "(%s) (%s) (%s) (%s)\n", $1,$2,$3,$4 }
' FS=, 
(aa=11) (bb=22) (cc=33)
(aa) (11,bb) (22,cc) (33)    # 필드값이 FS="=" 에 따라 재설정 되었다.

필드 변수에 대입 연산

FS 문자가 ; 인 레코드를 출력할 때 @ 문자로 바꾸어 출력하려면 어떻게 할까요? 아래 첫 번째 경우처럼 하게 되면 정상적으로 실행되지 않는 것을 볼 수 있습니다.

# 정상적으로 실행되지 않는다.
$ echo "11;22;33" | awk -vFS=';' -vOFS='@' 1 
11;22;33

# 다음과 같이 해야 합니다.
$ echo "11;22;33" | awk -vFS=';' -vOFS='@' '{ print $1,$2,$3 }' 
11@22@33

$ echo "11;22;33" | awk -vFS=';' -vOFS='@' '{$1=$1}1' 
11@22@33

$1=$1, $2+=5, $3=$7="" 와 같이 필드 변수에 대입 연산을 하게 되면 레코드 값에 해당하는 $0 값이 OFS 에 따라 재설정됩니다. 이때는 $0 변수에 대입 연산을 하는 것이 아니므로 필드 변수들이 FS 에의해 재설정되지는 않습니다. 다시 말해서 $0 값은 변경되지만 기존의 필드 변수값들은 그대로 남아있습니다.

# 필드 변수에 대입 연산이 하는 역할은 실질적으로 다음과 같습니다.
$0 = $1 OFS $2 OFS $3 OFS ...
$ echo "   111  222 333" |
awk '{ printf "(%s) (%s) (%s)\n",$1,$2,$3
       printf "(%s)\n", $0 }'        
(111) (222) (333)       # 필드값은 제대로 분리되지만
(   111  222 333)       # $0 값은 원본 그대로 공백이 남아 있다.
..................................................................

$ echo "   111  222 333" |
awk '{ printf "(%s) (%s) (%s)\n",$1,$2,$3
       $1=$1
       printf "(%s)\n", $0 }' 
(111) (222) (333)
(111 222 333)           # $1=$1 를 실행하면 $0 값이 재설정 된다.
......................................................................

$ echo "   111  222 333" |
awk '{ printf "(%s) (%s) (%s)\n",$1,$2,$3
       $1=$1
       printf "(%s)\n", $0
       printf "(%s) (%s) (%s)\n",$1,$2,$3
}' OFS=':'              # OFS=':' 값 설정
(111) (222) (333)
(111:222:333)           # $0 값이 재설정 될 때 OFS 값이 사용되는 것을 볼 수 있습니다.
(111) (222) (333)       # $0 값이 변경되었지만 기존의 필드값들은 그대로 남아있습니다.

필드에 대입 연산을 하게 되면 한가지 불편한 점은 $0 값이 재설정됨에 따라 기존의 프린트 포멧이 망가진다는 점입니다.

$ ls -l | awk 1
total 88
-rw-r--r-- 1 jack jack     8 Jun 19  2013 qunit-1.11.0.css
-rw-r--r-- 1 jack jack 56908 Jun 19  2013 qunit-1.11.0.js
-rw-r--r-- 1 jack jack  4306 Dec 29 09:16 test1.html
-rw-r--r-- 1 jack jack  5476 Dec  7 08:09 test1.js

# 필드에 대입 연산으로 인해 기존의 프린트 포멧이 망가진다.
$ ls -l | awk 'NR != 1 { $5+=100; print }'
-rw-r--r-- 1 jack jack 108 Jun 19 2013 qunit-1.11.0.css
-rw-r--r-- 1 jack jack 57008 Jun 19 2013 qunit-1.11.0.js
-rw-r--r-- 1 jack jack 4406 Dec 29 09:16 test1.html
-rw-r--r-- 1 jack jack 5576 Dec 7 08:09 test1.js

보통 printf 문을 이용해 출력을 포멧하지만 포멧이 고정적이지 않고 출력되는 값에 따라서 달라질 경우 다음과 같은 방법을 사용할 수 있습니다.

$ ls -l | awk 'NR != 1 { $5 = sprintf( "%*s", length($5), $5+100); print}' \
          FPAT=' *[[:graph:]]+' OFS=""
-rw-r--r-- 1 jack jack   108 Jun 19  2013 qunit-1.11.0.css
-rw-r--r-- 1 jack jack 57008 Jun 19  2013 qunit-1.11.0.js
-rw-r--r-- 1 jack jack  4406 Dec 29 09:16 test1.html
-rw-r--r-- 1 jack jack  5576 Dec  7 08:09 test1.js

예제 )

FS 가 / 문자인 레코드에서 5 번째 필드값에 +5 하기

# 필드 변수에 대입 연산을 하게 되면 $0 값이 재설정 되므로 OFS=/ 도 설정해야 합니다.
$ awk -F/ '{ $5+=5 }1' OFS=/ file
03/Oct/2016:06:39:50-0500,cd/base/5/48/2.png,206,1514
03/Oct/2016:06:39:50-0500,cd/base/6/46/3.png,206,5796
03/Oct/2016:06:39:50-0500,cd/base/7/45/4.png,206,2252
.....

FS 문자를 변경하여 출력하기

# FS 가 ',' 문자일 경우 '-' 로 변경하여 출력
$ echo "111,222,333" | awk '$1=$1' FS=, OFS=-
111-222-333
---------------------------------------------

# FS 가 NUL 문자일 경우 space 로 변경하여 출력
$ cat /proc/`pidof pulseaudio`/cmdline
/usr/bin/pulseaudio--start--log-target=syslog

$ awk '{$1=$1}1' FS='\0' OFS=' ' /proc/`pidof pulseaudio`/cmdline
/usr/bin/pulseaudio --start --log-target=syslog

FS, OFS 가 기본값인 공백일 경우 특정 필드 삭제하고 재정렬 하기

# $0=$0 는 필드 재설정!, $1=$1 는 레코드 재설정!
$ echo '111 222 333 444 555 666' |
awk '{$1=$3=$6=""; $0=$0; print $1,$2,$3; print $0; $1=$1; print $0}'

222 444 555
 222  444 555 
222 444 555

FS, OFS 가 공백이 아닐 경우는 다음과 같이 해야 합니다.

$ echo "111,222,333,444,555,666" |
awk '{ $1=$3=$6="\a"                       # 1,3,6 번 필드 삭제를 위해 마크
      gsub(OFS "(\a" OFS ")+", OFS)        # gsub 함수를 이용해 마크한 필드 삭제
      gsub("^\a" OFS "|" OFS "\a$", "")    # gsub 함수는 결과를 $0 에 대입하므로
                                           # 필드가 자동으로 재정렬 됩니다.
      printf "(%s) (%s) (%s)\n", $1,$2,$3
      print $0
}' FS=, OFS=,

(222) (444) (555)   # 필드값이 3 개로 재정렬
222,444,555         # 레코드 값도 재정렬됨

firefox 실행파일의 cpu instructions 사용빈도 통계

# objdump -d 출력을 `cat -t` 명령을 이용해 분석해 보면 다음과 같이
# ^I (tab) 문자로 구분되어 있는 것을 알 수 있습니다.
$ objdump -d /usr/lib/firefox/firefox | cat -t

    3658:^I48 83 ec 08          ^Isub    $0x8,%rsp
    365c:^I48 8b 05 7d b9 21 00 ^Imov    0x21b97d(%rip),%rax
    3663:^I48 85 c0             ^Itest   %rax,%rax

$ objdump -d /usr/lib/firefox/firefox |
awk -F'\t' '$3 { FS=" "; $0=$3; a[$1]++; FS="\t" } \
END { for (i in a) print a[i], i}' | sort -rn

36719 mov
9057 lea
8570 add
8130 cmp
5730 callq
5350 je
4381 test
4365 xor
3448 jne
3111 pop
. . .
. . .
# /bin 디렉토리에 있는 모든 실행파일 통계
$ find /bin -type f -exec stat -c '%i %n' {} + |
    awk '!($1 in a) { a[$1]; print $2 }' |
    xargs objdump -d 2> /dev/null |
    awk -F'\t' '$3 { FS=" "; $0=$3; a[$1]++; FS="\t" } \
    END { for (i in a) print a[i], i}' | sort -rn

702355 mov
187434 callq
166436 lea
115877 test
109900 je
106160 xor
84808 pop
83070 cmp
76116 push
75223 jne
. . .
. . .

필드 range 추출과 삭제

Field 분리 메뉴에서 작성한 함수의 경우는 레코드($0), 필드 변수값 ($1,$2,$3 ... NF) 들이 변경되지 않고 그대로 남아있었는데요. 이번에는 레코드, 필드 값들을 모두 재설정합니다.

$ cat file
1,2,3,4,5,6,7,8,9
a,b,c,d,e,f,g,h,i
.................................................

# 레코드, 필드 재설정 하면서 3 ~ 6 range 추출하기
$ awk '
{ print subflds(3,6) }

function subflds(s,e,  i) {
    for (i=1; i<=NF; i++)
        if (i < s || i > e) $i="\a"

    gsub(OFS "(\a" OFS ")+", OFS)
    gsub("^\a" OFS "|" OFS "\a$", "")
    return $0
}
' FS=, OFS=, file

3,4,5,6
c,d,e,f

# 레코드, 필드 재설정 하면서 3 ~ 6 range 삭제하기
$ awk '
{ print delflds(3,6) }

function delflds(s,e) {
    while (s <= e) $(s++) = "\a" 
    gsub(OFS "(\a" OFS ")+", OFS)
    gsub("^\a" OFS "|" OFS "\a$", "")
    return $0
}
' FS=, OFS=, file

1,2,7,8,9
a,b,g,h,i