본문 바로가기
빌드노트 검색

빌드노트

날짜에서 읽은 08·09만 Bash 계산에 실패하는 이유

Bash는 선행 0이 붙은 정수 상수를 8진수로 읽어 08·09에서 오류를 낸다. 새 셸의 실제 비교를 바탕으로 한두 자리 십진 입력을 검증한 뒤 10#으로 변환하고, 계산하지 않을 날짜 문자열은 그대로 유지하는 방법을 설명한다.

Bash에서 날짜의 일·월을 읽어 계산하는데 07은 되고 08·09에서만 value too great for base가 나온다면, 산술식에 들어가는 문자열의 앞자리 0을 확인한다. Bash 산술식은 0으로 시작하는 정수 상수를 8진수로 해석한다. 십진 숫자를 받는 코드라면 먼저 입력을 검증하고 10#으로 진법을 지정한다. 파일 이름이나 화면에 쓸 값이라면 08을 문자열로 유지해도 된다.

07이 통과했다고 십진수로 계산된 것은 아니다

다음 명령은 Linux 또는 WSL의 Bash 터미널에서 실행한다. 새 Bash 안에 합성 문자열 08을 넣고 1을 더하는 입력이다. LC_ALL=C는 오류 메시지와 문자 판정 조건을 고정한다.

LC_ALL=C bash --noprofile --norc -c 'day=08; printf "%d\n" "$((day + 1))"'

직접 실행했을 때 숫자는 출력되지 않았고 종료 상태는 1이었다. 표준 오류는 다음과 같았다.

bash: line 1: 08: value too great for base (error token is "08")

8진수 한 자리에는 0부터 7까지만 올 수 있다. 그래서 07은 계산되지만 08과 09는 유효한 8진수 상수가 아니다. 입력이 너무 큰 십진수라서 발생한 오류가 아니다. GNU Bash 매뉴얼의 Shell Arithmetic (새 탭에서 열림)은 선행 0과 base#숫자 표기의 해석 규칙을 설명한다. 10#08은 08을 10진수로 읽으라는 뜻이다.

같은 계산을 입력마다 새 Bash에서 실행하고, day + 1과 10#$day + 1을 비교했다.

  • 07: 기본 계산은 8, 십진수 지정 계산도 8이었다.
  • 08: 기본 계산은 오류, 십진수 지정 계산은 9였다.
  • 09: 기본 계산은 오류, 십진수 지정 계산은 10이었다.
  • 10: 두 계산 모두 11이었다.

숫자가 출력돼도 해석한 진법까지 같다는 보장은 없다. 별도로 넣은 010은 기본 계산에서 8에 1을 더해 9, 십진수 지정 계산에서 10에 1을 더해 11이었다. 따옴표로 day="08"을 대입해도 저장된 값은 같은 문자열이므로 산술식의 진법 규칙이 바뀌지 않는다.

입력을 좁힌 다음 10#을 붙인다

자기 스크립트에서는 입력을 day에 받는 부분 바로 다음, 첫 산술식보다 앞에 검사를 둔다. 아래 코드는 일·월처럼 짧은 값을 위해 ASCII 십진 숫자 한두 자리만 허용한다. 해석할 정수는 0부터 99, 여기에 1을 더한 결과는 1부터 100으로 제한된다. 선행 0은 허용하되 빈 값·부호·공백·수식은 허용하지 않는다.

다음 전체 입력은 별도 Bash에서 검증과 변환을 실행한다. 자신의 입력으로 확인할 때는 day=08 한 줄만 바꾼다.

LC_ALL=C bash --noprofile --norc <<'BASH'
day=08
if [[ ! $day =~ ^[0-9]{1,2}$ ]]; then
  printf 'day must be 1 or 2 decimal digits\n' >&2
  exit 2
fi
day_num=$((10#$day))
printf 'text=%s number=%d plus_one=%d\n' "$day" "$day_num" "$((day_num + 1))"
BASH

실제 출력은 다음과 같았고 종료 상태는 0이었다. 원문 day에는 08이 남고, 계산에는 십진수로 해석한 day_num을 쓴다.

text=08 number=8 plus_one=9

=~ 오른쪽은 정규식이다. ^와 $로 문자열 전체를 검사하고, [0-9]{1,2}로 숫자의 종류와 길이를 제한한다. 이 정규식 전체를 따옴표로 감싸면 정규식 기호까지 문자로 비교하므로 위와 같이 둔다. Bash의 정규식 조건 설명 (새 탭에서 열림)

첫 대입을 각각 day='', day='+08', day='-08', day=abc, day='1+2', day=' 08'로 바꿔 실행하자 모두 오류 문구를 내고 종료 상태 2로 끝났다. 숫자 출력은 없었고, 별도로 확인한 실행 추적에서도 day_num을 대입하는 변환 단계에 도달하지 않았다. 부호가 있는 정수도 이 코드의 허용 대상에서 제외한다.

10# 자체는 입력 검증기가 아니다. Bash는 산술식에서 참조한 변수 값도 식으로 평가한다. 따라서 입력이 숫자인지 알아보려고 먼저 계산하거나, 임의 문자열에 접두어만 붙여 계산하지 않는다. 위의 1+2도 계산 전에 거부했다. 변수 값을 식으로 평가한다는 규칙 역시 Shell Arithmetic (새 탭에서 열림)에 나와 있다.

길이 제한도 의도한 조건이다. 010과 100은 숫자만 있지만 세 자리여서 위 검사를 통과하지 못했다. 더 긴 정수가 필요하면 업무상 상한과 계산 결과의 범위까지 정해 검사를 바꾼다. 매뉴얼에 따르면 Bash는 고정 폭 정수로 계산하며 오버플로를 검사하지 않는다. ^[0-9]+$처럼 숫자 여부만 검사하는 것으로 큰 정수의 정확성까지 보장할 수는 없다.

여기까지는 정수 표기 검사다. 00은 0으로, 32는 32로 변환됐다. 실제 날짜라면 일의 1~31, 월의 1~12 같은 범위를 추가로 확인해야 하며, 월별 말일과 윤년은 그보다 더 확인해야 한다. 31 + 1의 결과 32를 다음 날짜로 사용할 수는 없다.

표시할 08은 숫자로 바꾸지 않아도 된다

날짜를 파일 이름이나 로그에 붙이기만 한다면 계산 없이 원문을 출력한다. 다음 입력은 앞자리 0을 보존하는지 확인한다.

day=08
printf 'day=%s\n' "$day"

출력은 day=08이었다. %s로 문자열을 쓰면 진법을 선택할 필요가 없다. 앞의 변환 코드처럼 표시할 원문과 계산할 정수를 따로 두면, 계산을 위해 날짜 표기를 잃을 필요도 없다.

직접 실행한 환경은 WSL2 Ubuntu 24.04.4 LTS, Bash 5.2.21, LC_ALL=C다. 설명은 유지관리자 사이트의 GNU Bash 5.3 매뉴얼과 대조했다. Bash 5.3 실행, 다른 로케일·셸, 부호 있는 수의 변환, 큰 정수의 경계와 실제 달력 날짜 계산은 검증하지 않았다. [[ ... =~ ... ]]와 10#을 POSIX sh에서도 그대로 쓸 수 있다고 가정하지 않는다.

← 전체 글 보기