Devin.KR
로그인

리눅스 bc 명령어 사용법 - 셸에서 소수점 계산과 진법 변환하기

개발자 조회 1

이 명령어를 언제 쓰는가

셸 스크립트에서 디스크 사용률을 소수점 한 자리까지 계산하고 싶을 때, 바이트를 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, 거짓이면 0echo "5 > 3" | bc → 1
-la(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" | bc0.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).