Skip to content
isdnetworks
Go back

범위를 넓히는 조용한 실패

(수정됨)

작업 폴더 루트에 흩어진 산출물을 artifacts/ 로 옮기는 스크립트를 실행했더니 mv 가 저장소 루트 자체를 옮기려 했다. 목적지가 자기 하위 경로여서 오류로 무산됐는데 목적지가 달랐으면 저장소가 통째로 옮겨졌을 것이다.

Table of contents

Open Table of contents

원인 — 빈 배열이 만든 인자

스크립트는 이런 모양이었다.

mapfile -t files < <(ls "$R")
for f in "${files[@]}"; do
  mv "$R/$f" "$R/artifacts/"
done

mapfile 은 GNU bash 매뉴얼의 Bash Builtin Commands 항목에 올라 있는 bash 내장이라 zsh 로 실행하면 command not found 가 나고 files 는 선언조차 되지 않는다. 그런데 zsh 5.9 에서 선언되지 않은 배열을 "${files[@]}" 로 순회하면 루프가 0회 도는 것이 아니라 빈 문자열 한 번을 돈다(선언된 빈 배열은 0회). 그 상태에서 $f 가 빈 문자열이면 mv 의 인자가 $R/ 곧 저장소 루트가 된다.

침묵의 구조

command not found 자체는 났지만 그 뒤가 조용했다. 스크립트가 거기서 멈추지 않았고 배열이 비었다는 사실을 어디에서도 알려 주지 않았다.

실패가 오류로 드러나지 않고 다음 단계로 그냥 흘러간 것이 이 함정의 핵심이다. 오류가 났다는 사실과 그 오류가 뒤를 막았다는 사실은 별개이고, 막지 않으면 앞 단계의 실패가 뒤 단계의 입력이 된다.

같은 형태의 다른 사례

변수 이름이 겹친 경우가 있었는데 저장소 스무 개 남짓을 대조하는 스크립트에서 경로를 담은 path 를 루프 카운터가 덮어 첫 항목만 정상이고 나머지가 전부 실패했다. 그것을 보고 디스크가 빠졌다고 판단해 하드웨어 장애를 보고했는데 디스크는 멀쩡했다.

여기서 진단 신호를 하나 얻었다. 첫 항목만 성공하고 나머지가 똑같이 실패하면 환경이 아니라 스크립트를 먼저 의심한다. 환경 장애면 첫 항목도 같이 실패했어야 한다.

set -e 가 스크립트를 죽인 경우도 있었다. 없는 컨테이너를 docker inspect 로 조회했더니 종료 코드가 0이 아니라서 스크립트 전체가 그 자리에서 끝났고 뒷단계가 실행되지 않았는데 출력만 봐서는 조용히 끝난 것처럼 보였다.

조치 — 파괴적 명령의 가드

mv 와 rm 과 cp 에 변수를 넣을 때 빈 값 검사는 선택이 아니다.

[ -n "$f" ] || { echo "빈 값 - 중단"; exit 1; }
mv "$R/$f" "$R/artifacts/"

한 줄짜리 검사지만 이것이 없으면 앞에서 본 사고가 그대로 재현된다. 여기에 더해 대상을 명시 목록으로 만든다. 목록을 만드는 데는 두 가지 방법이 있었는데 ls 출력을 그 자리에서 순회하는 것과 find 결과를 list.txt 에 적고 wc -l 로 개수를 본 뒤 while read 로 건별 처리하는 것이다. 뒤쪽을 골랐다. 이유는 목록이 비었을 때 개수 0 이 눈에 보여서다. 대신 명령이 한 번에서 세 번으로 늘어나는 것을 감수했다. 근거는 파일에 적힌 목록이 비었을 때 0 줄이라는 사실이 눈에 보인다는 점이다.

실행 셸 확인

이 스크립트를 돌리는 도구가 zsh로 동작한다는 것을 몰랐던 것이 시작이었다. mapfile 과 readarray 와 declare -A 같은 것은 bash 전용이라 zsh에서는 없거나 다르게 동작한다. 쓰기 전에 실행 셸을 확인하거나 두 셸에서 모두 되는 문법만 쓴다.

정리


Share this post on:

Previous Post
Docker host 모드에서 VPN 트래픽이 전달되지 않는 문제 해결
Next Post
통과했지만 아무것도 검증 못 한 테스트