리눅스 bc 명령어 사용법 - 셸에서 소수점 계산과 진법 변환하기
이 명령어를 언제 쓰는가
셸 스크립트에서 디스크 사용률을 소수점 한 자리까지 계산하고 싶을 때, 바이트를 GB로 환산할 때, 넷마스크를 2진수로 펼쳐 보고 싶을 때 bc를 쓴다.
이유는 단순하다. bash의 산술 확장 $(( ))는 정수만 다룬다. $((3/2))는 1이 되고 $((0.5+0.5))는 아예 문법 오류다. 소수점이 필요한 순간 선택지는 bc나 awk, 아니면 python 정도인데, bc는 거의 모든 리눅스에 기본으로 있고 표현식만 넘기면 되어 가볍다.
bc는 임의 정밀도 계산기다. 64비트 정수 범위를 넘는 큰 수도 정확히 계산한다.
기본 형식
echo "표현식" | bc [옵션]
bc [옵션] <<< "표현식"
bc는 표준 입력에서 표현식을 읽어 결과를 표준 출력에 찍는다. 스크립트에서는 파이프로 넘기는 형태가 가장 많다.
- scale — 소수점 이하 자리수. 기본값이 0이라 나눗셈이 그냥 잘린다. 표현식 앞에
scale=2;처럼 붙여 준다. - -l — 수학 라이브러리를 읽어 들인다.
s(),c(),a(),l(),e(),j()를 쓸 수 있고(sqrt()는-l없이도 되는 내장 함수다), 동시에 scale이 20으로 바뀐다. - -q — GNU bc의 시작 배너를 숨긴다. 대화형으로 띄울 때만 의미가 있다.
자주 쓰는 문법
| 문법 | 의미 | 예시와 결과 |
|---|---|---|
scale=n | 소수점 자리수 지정 | echo "scale=4; 3/2" | bc → 1.5000 |
^ | 거듭제곱(지수는 정수만) | echo "2^10" | bc → 1024 |
sqrt(x) | 제곱근 | echo "scale=4; sqrt(2)" | bc → 1.4142 |
% | 나머지. scale의 영향을 받는다 | echo "10 % 3" | bc → 1 |
ibase=n | 입력 진법 | echo "ibase=16; FF" | bc → 255 |
obase=n | 출력 진법 | echo "obase=2; 10" | bc → 1010 |
> < == != | 비교. 참이면 1, 거짓이면 0 | echo "5 > 3" | bc → 1 |
-l과 a(1) | 아크탄젠트. 원주율 구할 때 | echo "scale=10; 4*a(1)" | bc -l → 3.1415926532 |
실전 예제
1) 사용률을 소수점 한 자리까지 계산한다. 임계값 알림 스크립트의 기본 형태다. 곱셈을 나눗셈보다 먼저 하는 것이 요령이다.
used=4312
total=8192
pct=$(echo "scale=1; $used * 100 / $total" | bc)
echo "메모리 사용률 ${pct}%"
메모리 사용률 52.6%
$used / $total * 100 순서로 쓰면 scale=1에서 0.5 * 100 = 50.0이 되어 정밀도를 잃는다.
2) 바이트를 사람이 읽는 단위로 환산한다. du -sb나 API가 돌려준 바이트 값을 보고할 때 쓴다.
bytes=17825792000
echo "scale=2; $bytes / 1024 / 1024 / 1024" | bc
16.60
3) 셸의 if 문에서 소수 비교를 한다. [ 52.6 -gt 80 ]는 integer expression expected 오류가 난다. bc의 비교 결과 1/0을 받아 판단한다.
load=$(uptime | awk -F'load average: ' '{print $2}' | cut -d, -f1)
if [ "$(echo "$load > 4.0" | bc)" -eq 1 ]; then
echo "부하 경고: $load"
fi
4) 진법을 변환한다. 넷마스크나 권한 비트를 2진수로 펼쳐 확인할 때 쓴다.
echo "obase=2; 192" | bc
echo "obase=16; 255" | bc
echo "ibase=16; FF" | bc
11000000
FF
255
5) 64비트를 넘는 큰 수를 정확히 계산한다. 해시 공간 크기나 조합 수를 어림할 때 쓴다. $(( ))는 여기서 오버플로가 난다.
echo "2^100" | bc
1267650600228229401496703205376
6) 여러 줄 계산을 함수로 묶는다. 같은 변환을 반복할 때 읽기 쉬워진다.
bc <<'EOF'
scale=2
define gb(b) { return b / 1024 / 1024 / 1024 }
gb(17825792000)
gb(536870912000)
EOF
16.60
500.00
함정과 주의점
scale의 기본값이 0이라 나눗셈이 통째로 잘린다. echo "3/2" | bc는 반올림이 아니라 버림으로 1을 준다. echo "1/3*3" | bc는 0이다. 나눗셈이 하나라도 들어가면 scale=을 먼저 쓰는 것을 규칙으로 삼는다. 반대로 -l을 주면 scale이 20이 되어 0.66666666666666666666 같은 긴 값이 나오므로, -l을 쓰면서도 자리수를 통제하려면 scale을 다시 지정한다.
% 연산자가 scale에 끌려간다. 이걸 모르면 원인을 절대 못 찾는다.
echo "10 % 3" | bc # 1 (기대한 값)
echo "scale=2; 10 % 3" | bc # .01 (?)
bc의 %는 a - (a/b)*b로 정의되는데, 이때 a/b가 현재 scale을 따르기 때문이다. scale=2에서는 10/3이 3.33이 되고 10 - 9.99 = .01이 된다. 나머지 연산이 필요하면 같은 표현식에서 scale을 쓰지 말고 셸의 $((10 % 3))을 쓰는 편이 안전하다.
1 미만의 결과에 앞자리 0이 안 붙는다. echo "scale=2; 1/2" | bc는 0.50이 아니라 .50이다. 이 값을 그대로 JSON이나 로그에 넣으면 파서가 거부하거나 보기 흉해진다. 출력 단계에서 printf로 감싼다.
printf '%.2f\n' "$(echo "scale=4; 2/3" | bc)"
0.67
덤으로 printf는 반올림까지 해 준다. bc는 버림이라는 점과 함께 기억해 둘 만하다.
긴 숫자가 70자에서 백슬래시로 줄바꿈된다. 큰 수를 변수에 담을 때 조용히 깨지는 지점이다.
echo "2^400" | bc
25822498780869085896559191720030118743297057928292235128306593565406\
47622016841194629645353280137831435903171972747493376
줄 끝의 백슬래시와 개행이 그대로 들어간다. 환경변수 BC_LINE_LENGTH=0을 주면 한 줄로 나온다.
BC_LINE_LENGTH=0 bc <<< "2^400"
ibase와 obase를 함께 쓸 때 순서가 결과를 바꾼다. ibase를 먼저 설정하면 그 뒤의 숫자가 전부 새 진법으로 해석된다. 그래서 이런 일이 벌어진다.
echo "ibase=16; obase=10; FF" | bc # FF (obase의 10이 16진수 16으로 읽힘)
echo "obase=10; ibase=16; FF" | bc # 255 (의도한 결과)
obase를 항상 먼저 쓴다. 그리고 16진수 입력은 대문자만 받는다. echo "ibase=16; ff" | bc는 오류 없이 0을 돌려준다.
bc가 없는 컨테이너가 많다. alpine이나 slim 계열 이미지에는 bc가 빠져 있는 경우가 흔하다. 의존성을 늘리기 싫으면 awk로 대체할 수 있다. awk 'BEGIN{printf "%.1f\n", 4312*100/8192}'는 같은 결과를 주고 어디에나 있다. 배포판 차이도 있다. 리눅스의 bc는 대개 GNU bc라 -q 없이 실행하면 대화형에서 저작권 배너를 찍지만, macOS·FreeBSD에 들어 있는 bc는 다른 구현이라 배너가 없다. 스크립트에서 파이프로 쓸 때는 어느 쪽이든 배너가 나오지 않으므로 실제 문제는 되지 않는다.
함께 보면 좋은 명령어
- awk — 계산이 한두 개뿐이거나 텍스트 처리와 섞여 있으면 awk 하나로 끝내는 편이 프로세스도 덜 띄우고 읽기도 낫다.
- printf — bc 결과의 자리수 정리와 반올림, 앞자리 0 보정을 맡는다. 짝으로 쓴다.
- numfmt — 바이트를 GB로 바꾸는 정도라면
numfmt --to=iec가 bc보다 간단하다(GNU coreutils).