예제 파일

이 장의 예제는 xyz 파일 두 개와 ORCA 출력 하나를 쓴다. opt.out은 ORCA 가이드의 물 구조 최적화 + 진동수 계산 출력(3623줄)이다.

$ cat methanol.xyz
6
methanol
C     -0.046520    0.662749    0.000000
O     -0.046520   -0.754989    0.000000
H     -1.086345    0.976632    0.000000
H      0.437275    1.070835    0.889981
H      0.437275    1.070835   -0.889981
H      0.861339   -1.051121    0.000000

xyz 파일은 첫 줄이 원자 수, 둘째 줄이 주석, 셋째 줄부터 원소 기호와 x, y, z 좌표(Å)다. water.xyz도 같은 형식에 원자가 세 개다.

grep: 줄 찾기

grep 패턴 파일은 패턴이 들어 있는 줄을 그대로 출력한다.

$ grep -c '^H' methanol.xyz
4
$ grep -n '^O' water.xyz methanol.xyz
water.xyz:3:O      0.000000    0.000000    0.119262
methanol.xyz:4:O     -0.046520   -0.754989    0.000000
$ grep -v '^H' methanol.xyz
6
methanol
C     -0.046520    0.662749    0.000000
O     -0.046520   -0.754989    0.000000

자주 쓰는 옵션은 많지 않다.

옵션동작
-i대소문자 무시
-n줄 번호를 함께 출력
-c맞는 줄의 개수만 출력
-v맞지 않는 줄을 출력
-l맞는 줄이 있는 파일 이름만 출력
-r디렉터리 아래 파일을 모두 검색
-A N, -B N맞는 줄 뒤(After), 앞(Before)으로 N줄 더 출력
-w단어 전체가 맞을 때만
-E확장 정규식 (+, |, ()를 그대로 씀)
-F패턴을 정규식이 아닌 고정 문자열로 취급

-r -l은 디렉터리 여러 개에서 어떤 계산이 끝났는지 볼 때 쓴다. -A와 -B는 표 머리글 아래 몇 줄을 함께 보고 싶을 때 쓴다.

$ grep -r -l 'TERMINATED NORMALLY' ../orca
../orca/05-casscf-h2/h2_casscf.out
../orca/04-solvation-smd/water.out
../orca/03-dlpno-ccsdt/water.out
../orca/02-opt-freq/water.out
../orca/01-single-point/water.out
$ grep -B1 -A1 'THE OPTIMIZATION HAS CONVERGED' opt.out
                    ***********************HURRAY********************
                    ***        THE OPTIMIZATION HAS CONVERGED     ***
                    *************************************************
$ grep -c 'FINAL SINGLE POINT ENERGY' opt.out
5

마지막 줄의 5는 최적화 네 사이클과 수렴 후 마지막 에너지 계산을 합친 수다. -w는 패턴 앞뒤가 단어 경계일 때만 맞춘다. 아래 입력 줄의 TightOpt에는 Opt가 들어 있지만 단어 전체는 아니어서, 두 번째 명령은 아무것도 출력하지 않는다.

$ grep 'Opt' water.inp
! B3LYP D4 def2-TZVP TightOpt Freq
$ grep -w 'Opt' water.inp

정규식 기초

grep, sed, awk는 패턴을 정규식으로 읽는다. 계산 출력에서 쓰는 것은 아래 정도다.

기호뜻예
.아무 문자 하나H.는 H1, He, H
*앞 문자가 0번 이상^H.*-는 H로 시작하고 뒤에 -가 있는 줄
+앞 문자가 1번 이상 (-E)[0-9]+는 숫자 한 개 이상
[...]괄호 안 문자 중 하나[CHO], [0-9.]
^, $줄의 시작, 끝^H, Eh$
\s공백 문자 (GNU 확장)^\s+는 줄 앞 들여쓰기
a|ba 또는 b (-E)^(C|O)
$ grep -E '^(C|O) ' methanol.xyz
C     -0.046520    0.662749    0.000000
O     -0.046520   -0.754989    0.000000
$ grep -E '^\s+[0-9]+:\s+[0-9.]+ cm' opt.out
     0:       0.00 cm**-1
     1:       0.00 cm**-1
     2:       0.00 cm**-1
     3:       0.00 cm**-1
     4:       0.00 cm**-1
     5:       0.00 cm**-1
     6:    1615.68 cm**-1
     7:    3780.78 cm**-1
     8:    3885.42 cm**-1

찾는 문자열에 *, ., [ 같은 기호가 들어 있으면 -F를 붙인다. 붙이지 않으면 cm**-1의 *가 반복 기호로 읽혀 엉뚱한 줄이 걸린다.

$ grep 'cm**-1' opt.out | head -3
Rotational constants in cm-1:            27.545905            14.281345             9.405174 
Rotational constants in cm-1:    27.545929    14.281357     9.405182 
Chem.Eur.J. 2012 18 9955 using a reference frequency of 100.0 cm-1
$ grep -F 'cm**-1' opt.out | head -3
     0:       0.00 cm**-1
     1:       0.00 cm**-1
     2:       0.00 cm**-1
패턴은 작은따옴표로

패턴을 작은따옴표로 감싸면 셸이 *, $를 먼저 해석하지 않는다. 셸 변수를 패턴에 넣어야 할 때만 큰따옴표를 쓴다.

sed: 줄 고치기

sed는 파일을 한 줄씩 읽어 명령을 적용하고 결과를 화면에 낸다. 원본은 바뀌지 않는다. 가장 많이 쓰는 명령은 치환 s/찾을것/바꿀것/이다.

$ sed 's/0/X/' water.xyz
3
water
O      X.000000    0.000000    0.119262
H      X.000000    0.763239   -0.477047
H      X.000000   -0.763239   -0.477047
$ sed 's/0/X/g' water.xyz
3
water
O      X.XXXXXX    X.XXXXXX    X.119262
H      X.XXXXXX    X.763239   -X.477X47
H      X.XXXXXX   -X.763239   -X.477X47

g가 없으면 줄마다 첫 번째만 바뀐다. 바꿀 내용에 /가 있으면 구분자를 다른 문자로 바꾸는 편이 읽기 쉽다.

$ sed 's|water|water, B3LYP-D4/def2-TZVP|' water.xyz
3
water, B3LYP-D4/def2-TZVP
O      0.000000    0.000000    0.119262
H      0.000000    0.763239   -0.477047
H      0.000000   -0.763239   -0.477047

-n은 자동 출력을 끄고, p는 해당 줄을 출력한다. 둘을 같이 쓰면 원하는 줄만 뽑힌다. 주소는 줄 번호(3,5), 마지막 줄($), 정규식(/^H/), 정규식 두 개 사이의 범위(/시작/,/끝/)로 줄 수 있다. d는 그 줄을 지운다.

$ sed -n '3,5p' methanol.xyz
C     -0.046520    0.662749    0.000000
O     -0.046520   -0.754989    0.000000
H     -1.086345    0.976632    0.000000
$ sed '/^H/d' methanol.xyz
6
methanol
C     -0.046520    0.662749    0.000000
O     -0.046520   -0.754989    0.000000
$ sed -n '/VIBRATIONAL FREQUENCIES/,/NORMAL MODES/p' opt.out
VIBRATIONAL FREQUENCIES
-----------------------

Scaling factor for frequencies =  1.000000000  (already applied!)

     0:       0.00 cm**-1
     1:       0.00 cm**-1
     2:       0.00 cm**-1
     3:       0.00 cm**-1
     4:       0.00 cm**-1
     5:       0.00 cm**-1
     6:    1615.68 cm**-1
     7:    3780.78 cm**-1
     8:    3885.42 cm**-1


------------
NORMAL MODES

파일을 직접 고치기: -i

-i를 주면 결과를 화면 대신 원본 파일에 쓴다. 되돌릴 수 없으니 -i.bak처럼 확장자를 붙여 백업을 남기는 습관을 들인다.

$ sed -i.bak '2s/.*/renamed/' w2.xyz
$ ls w2.xyz*
w2.xyz
w2.xyz.bak
$ head -2 w2.xyz
3
renamed
macOS의 sed는 -i가 다르다

Linux의 GNU sed는 sed -i 's/a/b/' f로 백업 없이 고친다. macOS의 BSD sed는 -i 뒤에 백업 확장자를 반드시 받기 때문에, 백업 없이 쓰려면 sed -i '' 's/a/b/' f라고 써야 한다. sed -i.bak은 두 쪽에서 똑같이 동작하므로 스크립트에는 이 형태가 안전하다.

awk: 열 단위 계산

awk는 각 줄을 공백 기준으로 나눠 $1, $2, … 에 담는다. $0은 줄 전체, NF는 그 줄의 열 개수, $NF는 마지막 열, NR은 지금까지 읽은 줄 번호다. 프로그램은 패턴 {동작}의 나열이고, 패턴이 맞는 줄에만 동작이 실행된다.

$ awk 'NR > 2 {print NR, NF, $1, $NF}' methanol.xyz
3 4 C 0.000000
4 4 O 0.000000
5 4 H 0.000000
6 4 H 0.889981
7 4 H -0.889981
8 4 H 0.000000
$ awk '/^H/ {print $2}' methanol.xyz
-1.086345
0.437275
0.437275
0.861339
$ awk '$1 == "H" && $4 > 0' methanol.xyz
H      0.437275    1.070835    0.889981

동작을 생략하면 줄 전체를 출력한다. 패턴을 조건식으로 쓸 때는 머리글 줄을 조심한다. NR > 2 없이 $4 < 0만 쓰면 열이 없는 첫 두 줄도 걸린다.

$ awk '$4 < 0' methanol.xyz
6
methanol
H      0.437275    1.070835   -0.889981

구분자가 공백이 아니면 -F로 준다. -F,는 CSV, -F:는 콜론이다. 셸 변수는 -v 이름=값으로 넘긴다.

BEGIN, END, 합과 평균

BEGIN 블록은 첫 줄을 읽기 전에, END 블록은 마지막 줄 뒤에 한 번 실행된다. 변수는 선언 없이 0에서 시작하므로 합계와 개수를 모았다가 END에서 나누면 평균이다.

$ awk 'NR > 2 {z += $4; n++} END {print z, n, z/n}' water.xyz
-0.834832 3 -0.278277
$ awk 'NR > 2 {z += $4; n++} END {printf "mean z = %.4f over %d atoms\n", z/n, n}' water.xyz
mean z = -0.2783 over 3 atoms

printf는 C와 같은 형식 문자열을 쓴다. %d는 정수, %.4f는 소수점 넷째 자리, %-8s는 8칸 왼쪽 정렬 문자열, %12.6f는 12칸 오른쪽 정렬이다. 줄바꿈 \n은 직접 넣는다.

$ awk 'BEGIN {print "element  x"} NR > 2 {printf "%-8s %8.3f\n", $1, $2}' methanol.xyz
element  x
C          -0.047
O          -0.047
H          -1.086
H           0.437
H           0.437
H           0.861

소수 계산은 awk로

bash의 산술 $(( ))은 정수만 다룬다. 나눗셈은 버림이 되고, 소수점이 있으면 오류가 난다.

$ echo $(( 3 / 2 ))
1
$ echo $(( -76.426 * 627.5 ))
bash: line 1: -76.426 * 627.5 : arithmetic syntax error: invalid arithmetic operator (error token is ".426 * 627.5 ")
$ awk 'BEGIN {print -76.426369457144 * 627.5095}'
-47958.3
$ awk 'BEGIN {printf "%.2f\n", -76.426369457144 * 627.5095}'
-47958.27

awk는 모든 수를 배정도 실수로 계산한다. 다만 print는 유효숫자 여섯 자리까지만 보여 주므로, 에너지처럼 자릿수가 중요한 값은 printf로 자릿수를 정해서 출력한다.

sort · uniq · cut · paste · tr · column · wc

grep, sed, awk 사이에 끼워 쓰는 작은 도구들이다.

명령하는 일
sort줄 정렬. -n 숫자, -g 지수 표기 포함 숫자, -r 역순, -k4,4 넷째 열 기준
uniq연속된 중복 줄 제거. -c는 개수를 붙인다. 보통 sort 뒤에 쓴다
cut열 자르기. -d, -f2는 쉼표 구분 둘째 열, -c1-2는 1–2번째 글자
paste파일 여러 개를 옆으로 붙인다. -d,로 구분자 지정
tr문자 단위 바꾸기. tr 'A-Z' 'a-z', tr ' ' '\n'
column -t열 맞춰 보기 좋게 출력. -s,로 구분자 지정
wc -l줄 수 세기
$ tail -n +3 methanol.xyz | awk '{print $1}' | sort | uniq -c
      1 C
      4 H
      1 O
$ tail -n +3 methanol.xyz | sort -k4,4g
H      0.437275    1.070835   -0.889981
C     -0.046520    0.662749    0.000000
H      0.861339   -1.051121    0.000000
H     -1.086345    0.976632    0.000000
O     -0.046520   -0.754989    0.000000
H      0.437275    1.070835    0.889981
$ paste -d, el.txt z.txt
O,0.119262
H,-0.477047
H,-0.477047
$ printf 'name,E\nwater,-76.3213\nmethanol,-115.5\n' | column -t -s,
name      E
water     -76.3213
methanol  -115.5

tail -n +3은 셋째 줄부터 끝까지를 낸다. xyz 머리글 두 줄을 건너뛸 때 편하다. uniq는 붙어 있는 중복만 합치므로 앞에 sort가 없으면 개수가 틀린다.

xyz 파일로 연습

원소별 원자 수

위의 sort | uniq -c를 awk 하나로 할 수도 있다. awk 배열은 문자열을 키로 받는다. for (e in n)의 순서는 정해져 있지 않으니 순서가 필요하면 뒤에 sort를 붙인다.

$ awk 'NR > 2 {n[$1]++} END {for (e in n) print e, n[e]}' methanol.xyz | sort
C 1
H 4
O 1

좌표 이동

물 분자를 z 방향으로 1.5 Å 옮긴다. 머리글 두 줄은 그대로 출력하고 next로 다음 줄로 넘어간다.

$ awk -v dz=1.5 'NR <= 2 {print; next} {printf "%-2s %12.6f %12.6f %12.6f\n", $1, $2, $3, $4 + dz}' water.xyz
3
water
O      0.000000     0.000000     1.619262
H      0.000000     0.763239     1.022953
H      0.000000    -0.763239     1.022953

기하 중심을 원점으로 옮기려면 평균을 먼저 알아야 하므로 파일을 두 번 읽는다. NR == FNR은 첫 번째 파일을 읽는 동안만 참이다(FNR은 파일마다 1부터 다시 센다).

$ awk 'NR == FNR {if (FNR > 2) {x += $2; y += $3; z += $4; n++}; next}
       FNR <= 2 {print; next}
       {printf "%-2s %12.6f %12.6f %12.6f\n", $1, $2 - x/n, $3 - y/n, $4 - z/n}' methanol.xyz methanol.xyz
6
methanol
C     -0.139271     0.333592     0.000000
O     -0.139271    -1.084146     0.000000
H     -1.179096     0.647475     0.000000
H      0.344524     0.741678     0.889981
H      0.344524     0.741678    -0.889981
H      0.768588    -1.380278     0.000000

다른 형식으로 바꾸기

ORCA 입력의 좌표 블록(* xyz 전하 다중도 … *)으로 바꾼다. 전하 0, 단일항을 가정했다.

$ awk 'NR == 1 {print "* xyz 0 1"} NR > 2 {printf "  %-2s %12.6f %12.6f %12.6f\n", $1, $2, $3, $4} END {print "*"}' methanol.xyz
* xyz 0 1
  C     -0.046520     0.662749     0.000000
  O     -0.046520    -0.754989     0.000000
  H     -1.086345     0.976632     0.000000
  H      0.437275     1.070835     0.889981
  H      0.437275     1.070835    -0.889981
  H      0.861339    -1.051121     0.000000
*

스프레드시트나 Python에서 읽을 CSV가 필요하면 번호를 붙여 쉼표로 잇는다.

$ awk 'BEGIN {print "idx,el,x,y,z"} NR > 2 {print NR - 2 "," $1 "," $2 "," $3 "," $4}' water.xyz
idx,el,x,y,z
1,O,0.000000,0.000000,0.119262
2,H,0.000000,0.763239,-0.477047
3,H,0.000000,-0.763239,-0.477047

awk에서 값을 공백 없이 나란히 쓰면 문자열이 이어 붙는다. print a, b처럼 쉼표를 쓰면 사이에 공백이 들어간다. 실제 출력 파일에서 에너지와 구조를 뽑는 방법은 다음 장에서 다룬다.