<!doctype html>
2026년 7월 6일 | 개발 깨알 상식_Tips
reports/*.json 같은 패턴이 파일을 하나도 찾지 못했는데도, 셸은 그 문자열을 그대로 다음 명령에 넘길 수 있다. 나는 이걸 처음 겪었을 때 파일이 없다는 사실보다 에러 위치가 더 헷갈렸다. 분명히 앞 단계에서 결과가 비어 있었는데, 뒤쪽 로그에는 reports/*.json이라는 파일을 열 수 없다고 나왔다. 실제 파일명도 아닌 패턴 문자열인데, 실패 로그만 보면 마치 그런 경로가 있어야 했던 것처럼 보인다.
이 작은 차이는 배치 스크립트에서 은근히 귀찮다. 삭제, 복사, 압축, 업로드, 통계 집계처럼 “해당 파일들 전부”를 대상으로 삼는 단계는 보통 와일드카드로 시작한다. 문제는 파일이 없음이 정상인 상황과, 앞 단계가 깨져서 산출물이 안 나온 상황이 겉으로 비슷하게 보인다는 점이다. 그래서 와일드카드는 편한 축약 문법을 넘어, 후속 단계에 넘길 입력 목록을 만드는 작은 계약으로 보는 편이 안전하다.
파일이 없을 때도 루프가 도는 이유
Bash 기본 설정에서는 매칭되는 파일이 없을 때 glob 패턴이 그대로 남는다. 예를 들어 artifacts/*.csv가 아무 파일도 찾지 못하면, 많은 경우 루프 변수에는 실제 파일 경로가 아니라 그 문자열 자체가 들어간다. 그러면 루프는 0번이 아니라 1번 돈다. 안쪽 명령은 존재하지 않는 파일을 열려고 하고, 실패 메시지는 뒤늦게 나온다.
이때 로그가 나쁜 방향으로 친절해진다. “파일 없음”은 맞는 말이지만, 실제 원인은 개별 파일 누락보다 입력 목록 생성 실패에 가깝다. 앞 단계 산출물이 0개였다는 사실, glob이 어떻게 확장됐는지, 그 상태를 정상으로 볼지 실패로 볼지가 같이 남아야 한다. 이걸 분리하지 않으면 뒤쪽 도구의 에러만 계속 들여다보게 된다.
그대로, 빈 목록, 즉시 실패
매칭 없음은 적어도 세 가지 의미 중 하나로 정해야 한다. 첫째, 기본 Bash처럼 패턴을 그대로 남기는 방식이다. 대화형 탐색에서는 가끔 눈에 잘 띄지만, 자동화에서는 가짜 경로가 다음 단계까지 흘러가기 쉽다. 둘째, nullglob처럼 빈 목록으로 접는 방식이다. 후속 루프가 아예 돌지 않아서 깔끔하지만, 산출물이 반드시 있어야 하는 배치에서는 조용한 성공처럼 보일 수 있다.
셋째, failglob처럼 매칭 없음 자체를 즉시 실패로 보는 방식이다. 나는 결과 파일이 반드시 생겨야 하는 검증 단계에서는 이쪽이 더 편했다. 반대로 선택 입력이 있을 수도 있고 없을 수도 있는 정리 단계에서는 빈 목록이 자연스럽다. 중요한 건 옵션 이름보다 이 단계에서 0개가 정상인지를 먼저 정하는 일이다. 같은 패턴이라도 “오늘 생성된 리포트 업로드”와 “있으면 지울 캐시 파일 정리”에서는 답이 다르다.
팀 안에서 더 헷갈리는 부분은 셸마다 기본값이 다르다는 점이다. Bash 기준으로 작성한 스크립트를 zsh에서 손으로 돌리면 매칭 없음이 바로 에러처럼 보일 수 있고, 반대로 로컬에서 zsh로 안전하게 실패하던 명령이 CI의 Bash에서는 문자열 그대로 흘러갈 수 있다. 그래서 자동화에 들어가는 스크립트는 “내 터미널에서는 이렇게 됐다”보다 실행 셸과 glob 정책을 명시하는 편이 낫다. 이 차이를 적어 두면 운영체제나 CI 이미지가 바뀐 날에도 원인을 조금 빨리 좁힐 수 있다.
삭제 명령보다 목록 변수를 먼저 본다
위험한 순간은 보통 실제 작업 명령 앞에 온다. 삭제나 업로드 명령을 조심하는 것도 필요하지만, 나는 그보다 먼저 목록 변수를 만든 직후를 본다. 패턴을 바로 명령에 꽂지 않고, 한 번 배열이나 파일 목록으로 접은 뒤 개수를 확인하면 실패 원인이 훨씬 작아진다. 기대 최소 개수가 1개라면 0개에서 멈추고, 0개도 허용하는 정리 단계라면 “matched=0, skipped”처럼 남긴다.
이 기준은 특히 임시 디렉터리나 날짜별 산출물에서 좋았다. 경로가 맞는지, 날짜 prefix가 맞는지, 이전 실행 파일이 섞였는지까지 한 번에 볼 수 있기 때문이다. 목록 생성과 목록 처리를 분리해 두면, 실패 로그도 “glob 미매칭”과 “처리 도구 실패”로 나뉜다. 나중에 다시 봐도 어느 층을 열어야 하는지 바로 보인다.
파일 목록을 조금 더 명시적으로 만들고 싶을 때는 glob을 고집하지 않고 find 결과를 정렬해서 쓰는 편도 괜찮다. 이 방식은 하위 디렉터리 포함 여부, 숨김 파일 포함 여부, 수정 시간 조건처럼 기준이 늘어날 때 더 읽기 쉽다. 다만 이 경우에도 핵심은 같다. 결과 목록을 만들고, 그 목록의 개수를 확인하고, 0개 정책을 기록한 뒤 실제 작업으로 넘어가야 한다. 도구가 glob이든 find든, “대상이 없었다”가 뒤늦은 파일 열기 오류로 변하면 이미 한 단계 늦다.
내가 남기는 짧은 체크
작은 스크립트에서는 거창한 프레임워크보다 짧은 체크가 더 오래 간다. 패턴 문자열, 매칭된 파일 수, 허용되는 최소 개수, 0개일 때의 정책만 로그에 남겨도 충분한 경우가 많다. 예를 들어 “pattern=artifacts/*.csv, matched=0, policy=fail”과 “pattern=cache/*.tmp, matched=0, policy=skip”은 겉으로 둘 다 파일 없음이지만 운영 의미가 완전히 다르다.
나는 이런 체크를 넣을 때 와일드카드를 데이터 검증의 앞단으로 본다. 파일을 열어서 schema를 보기 전에, 애초에 열 파일 목록이 실제로 만들어졌는지 확인하는 단계다. 이 한 줄이 없으면 뒤쪽 오류가 자꾸 커진다. JSONDecodeError, 업로드 실패, 압축 파일 없음처럼 보이던 일이 사실은 “패턴이 아무것도 못 찾았다”로 끝나는 경우가 있다.
작게 정리한 기준
내 기준은 단순하다. 결과가 반드시 있어야 하는 단계에서는 매칭 없음이 실패다. 있을 수도 있고 없을 수도 있는 cleanup에서는 빈 목록을 정상으로 보되, 건너뛴 사실을 남긴다. 그리고 어떤 경우에도 매칭 안 된 패턴 문자열이 실제 파일 경로처럼 다음 단계에 들어가게 두지는 않는다. 이 기준 하나만 있어도 실패 로그의 첫 갈림점이 훨씬 선명해진다.
와일드카드는 편하지만, 자동화 안에서는 입력 목록을 만드는 작은 컴파일 단계에 가깝다. 그 단계가 비었는지, 실패했는지, 조용히 건너뛰어도 되는지를 앞에서 정하면 뒤쪽 로그가 훨씬 덜 시끄러워진다. 결국 중요한 건 옵션을 많이 아는 것이 아니라, 0개 매칭의 의미를 스크립트마다 명시하는 일이다.
사소한 방어선처럼 보여도 효과는 꽤 크다. 다음 사람이 로그를 열었을 때 “파일을 못 열었다”보다 “처음부터 대상이 없었다”를 먼저 읽을 수 있기 때문이다.
'[개발 깨알 상식_Tips]' 카테고리의 다른 글
| 중복 키가 조용히 덮어쓴 JSON 설정 (0) | 2026.07.13 |
|---|---|
| 16자리 ID가 바뀌는 JSON 파싱 오류 (0) | 2026.07.10 |
| 임시 디렉터리 잔여 파일과 테스트 착시 (0) | 2026.07.04 |
| 빈 환경변수가 덮어쓴 API 기본 URL (0) | 2026.07.03 |
| 릴리스 목록에서 1.10이 1.2보다 앞서는 문제 (0) | 2026.07.03 |