예제 파일
이 장의 예제는 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|b | a 또는 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
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처럼 쉼표를 쓰면 사이에 공백이 들어간다.
실제 출력 파일에서 에너지와 구조를 뽑는 방법은 다음 장에서 다룬다.