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

빌드노트

Bash에서 파일이 없는데도 *.log가 인자로 넘어간다

Bash는 *.log에 맞는 파일이 없으면 기본적으로 패턴 자체를 인자로 남긴다. 빈 폴더와 공백 포함 두 파일로 기본·nullglob·failglob를 비교하고, 배열 개수로 호출을 결정하는 방법과 failglob의 종료 상태 경계를 확인한다.

Bash에서 for file in *.log를 실행하면 로그 파일이 없을 때도 본문이 한 번 실행될 수 있다. 기본 설정은 일치하지 않은 패턴을 없애지 않고 문자열 *.log 그대로 남기기 때문이다. 빈 결과가 정상이라면 nullglob와 배열 개수 검사를 함께 쓰고, 파일이 반드시 있어야 한다면 failglob 오류를 확인한다.

파일 이름 확장(glob)은 대상 명령을 호출하기 전에 Bash가 한다. Bash 매뉴얼의 Filename Expansion (새 탭에서 열림)은 0건일 때 패턴을 남기는 기본 동작, 패턴을 제거하는 nullglob, 확장 오류를 내는 failglob를 구분한다.

빈 폴더인데 인자는 1개다

Linux의 Bash 터미널에서 다음을 실행한다. mktemp가 현재 위치 아래에 새 빈 폴더를 만들므로 쓰기 권한이 필요하다. 괄호는 디렉터리·옵션·인자 변경을 그 안에 한정한다. 실습 폴더는 삭제하지 않고 남긴다.

(
  lab=$(mktemp -d ./glob-demo.XXXXXX) || exit 1
  cd "$lab" || exit 1
  shopt -u nullglob failglob
  set -- *.log
  printf 'argc=%d\n' "$#"
  for file in "$@"; do printf '<%s>\n' "$file"; done
)

set --는 뒤에 오는 값을 위치 인자에 넣는다. $#는 그 개수이고, "$@"는 각 인자를 별개로 유지한다. Ubuntu 24.04.4의 Bash 5.2.21에서 실제 출력은 다음과 같았고, 괄호 묶음의 종료 상태는 0이었다.

argc=1
<*.log>

파일은 0개지만 인자는 1개다. 별도로 실행한 for file in *.log도 한 번 반복했고 변수에 *.log가 들어갔다. 성공 종료만 검사해서는 파일이 있었다고 판단할 수 없다.

같은 빈 폴더 조건에서 옵션 줄만 바꿔 비교했다. shopt -s는 켜기, -u는 끄기이며 두 옵션은 기본적으로 꺼져 있다. shopt의 설정 문법과 기본값 (새 탭에서 열림)

  • 기본: argc=1, <*.log>, 종료 상태 0. for는 1회였다.
  • shopt -u failglob; shopt -s nullglob: argc=0만 출력, 종료 상태 0. for는 0회였다.
  • shopt -u nullglob; shopt -s failglob: no match: *.log 오류가 났다. 인자를 설정하는 명령이 실행되지 않아 argc 출력도 없었고, 이 괄호 묶음의 종료 상태는 1이었다. for도 본문 실행 전에 확장 오류가 났다.

두 파일이 있는 조건도 비교하려면 첫 코드의 cd 다음에 : > "alpha one.log"와 : > "beta two.log"를 각각 한 줄씩 넣고 다시 실행한다. 새 폴더 안에 빈 파일 두 개를 만드는 입력이다. 세 설정 모두 argc=2 뒤에 <alpha one.log>, <beta two.log>를 한 줄씩 출력했고 종료 상태는 0이었다. for도 모두 두 번 반복했다. 공백을 포함한 이름 하나가 인자 하나로 유지됐다.

빈 결과를 허용할 때는 개수로 호출을 결정한다

nullglob는 일치하지 않은 패턴을 없앤다. 명령 실행 자체를 생략하지는 않는다. 별도 인자 출력 함수를 호출한 비교에서도 빈 폴더의 기본 설정은 인자 1개, nullglob는 인자 0개로 함수가 실행됐다. for에는 반복할 항목이 없어지지만, 일반 명령은 파일 인자 없이 실행될 수 있다.

처리할 폴더로 이동한 뒤, 배열에 결과를 담고 개수를 검사하면 호출 조건이 분명해진다. 다음은 파일을 처리하는 대신 경로만 출력한다. 실습에서는 첫 코드의 set -- *.log부터 for 줄까지를 아래 괄호 안의 내용으로 바꾸면 같은 빈 폴더·두 파일 조건을 확인할 수 있다.

(
  shopt -u failglob
  shopt -s nullglob
  files=(./*.log)
  printf 'matches=%d\n' "${#files[@]}"
  if ((${#files[@]})); then
    printf 'process=<%s>\n' "${files[@]}"
  fi
)

빈 폴더에서는 matches=0만 나왔다. 두 파일이 있으면 matches=2 뒤에 process=<./alpha one.log>, process=<./beta two.log>가 각각 출력됐다. 실제 처리 명령도 이 if 안에 놓고 배열을 "${files[@]}"로 전달한다. ${#files[@]}는 원소 수이며, 큰따옴표 안의 @ 확장은 원소마다 인자 하나를 만든다. Bash 배열 문법 (새 탭에서 열림)

출력 행 수를 인자 수로 대신 세지는 않는다. printf '<%s>\n'는 값 인자가 없어도 <> 한 줄을 출력했다. $#나 배열 길이를 확인해야 한다. 또 "*.log"처럼 패턴 자체를 인용하면 파일이 있어도 문자열 하나가 된다. 배열을 전달할 때 인용하는 것과 패턴을 인용하는 것은 역할이 다르다.

필수 입력이라면 failglob의 실패 상태를 확인한다

failglob는 0건인 명령의 실행을 막는다. 다만 이것만으로 모든 후속 줄이 중단된다고 가정하면 안 된다. 별도 비교에서는 확장 오류 다음의 독립된 printf 줄이 실행됐고, 자식 Bash의 최종 종료 상태가 0이었다. 반면 확장과 후속 출력을 같은 괄호 묶음 안에 놓으면 후속 출력 없이 종료 상태 1이었다.

따라서 필수 입력에는 failglob를 켜고, 확장과 그 결과에 의존하는 작업을 같은 묶음에 둔다. 호출한 쪽은 그 묶음이나 자식 Bash의 실패 상태를 확인한 뒤 다음 작업으로 넘어갈지 정한다. 이미 수행한 작업을 되돌려 주는 기능은 아니다.

결과가 다르면 bash --version으로 실행 셸을 확인하고 shopt -p nullglob failglob로 옵션을 읽는다. 패턴이 인용돼 있거나 set -f로 파일명 확장을 껐는지도 본다. 옵션이 꺼져 있으면 shopt -p 조회도 0이 아닌 상태를 반환할 수 있으므로 파일 검색 실패와 구분한다.

실행 범위는 WSL2 Ubuntu 24.04.4·Bash 5.2.21에서 새로 만든 빈 폴더와 공백 포함 두 파일이다. 운영 장애 기록이 아닌 합성 입력이며 실제 삭제는 하지 않았다. 참고한 유지관리자 사이트의 GNU Bash 매뉴얼은 5.3판이다. Bash 5.3, 다른 셸, 숨김 파일 설정·GLOBIGNORE 변경, 파일이 조회 중 바뀌는 상황은 실행하지 않았다. 이 배열·옵션 문법은 /bin/sh 스크립트에 그대로 적용하지 않는다.

← 전체 글 보기