bash while read 루프가 첫 줄만 처리하고 끝날 때 해결법

먼저 결론
증상: while read 로 목록 파일을 도는 스크립트가 첫 줄만 처리하고 오류 없이 "완료"로 끝납니다.
원인: 루프 안에서 부른 CLI 나 ssh 가 같은 표준입력, 곧 목록의 나머지 줄을 통째로 읽어 버립니다.
해결: 목록은 9번 같은 별도 통로로 읽고 안쪽 명령에는 /dev/null 을 주며, 루프 안 ssh 에는 -n 을 붙입니다.

목록 파일을 한 줄씩 읽어 일을 시키는 bash 루프는 아주 흔한 모양입니다. 그런데 안쪽에서 무거운 명령을 부르면 이 루프가 첫 줄만 하고 조용히 끝날 수 있습니다. 스크립트가 "끝났다"고 말하기 때문에 더 늦게 알아챕니다.

27개 중 1개만 하고 완료 알림까지 보냈다

목록 27개를 처리하는 일괄 스크립트가 1개만 처리하고 끝났습니다. 마지막 출력은 "올림 1 / 대상 27" 이었고, 스크립트는 정상 종료로 완료 알림까지 보냈습니다. 오류 메시지는 어디에도 없었습니다. 목록 파일 자체에는 문제가 없었고, 원인은 루프가 목록을 읽는 방식에 있었습니다.

여러 서버를 차례로 도는 다른 스크립트에서도 같은 일이 있었습니다. 첫 번째 서버 작업은 잘 됐는데, 두 번째 서버부터는 아무것도 하지 않고 루프가 끝났습니다.

안쪽 명령이 루프의 입력을 먹는다

while read ... done < 목록파일 은 목록 파일을 루프 전체의 표준입력으로 연결합니다. 루프 안에서 부른 명령도 따로 지정하지 않으면 이 표준입력을 물려받습니다.

ssh 나 일부 CLI 는 시작하면서 표준입력을 읽습니다. 그래서 첫 줄을 처리하던 명령이 목록의 나머지 줄을 전부 읽어 가 버리고, 다음 차례의 read 는 읽을 것이 없어 루프가 끝납니다. 아래처럼 간단히 재현할 수 있습니다.

printf 'a\nb\nc\n' > list.txt
while read -r x; do
  echo "처리: $x"
  cat > /dev/null   # 표준입력을 읽는 명령의 대역
done < list.txt
# 출력: 처리: a  (b, c 는 처리되지 않음)
안쪽 명령 표준입력을 읽나 대책
ssh 읽음 ssh -n 또는 < /dev/null
표준입력을 읽는 CLI 읽음 < /dev/null
rsync -e "ssh ..." rsync 가 표준입력을 씀 -e 안의 ssh 에는 -n 을 붙이지 않음

목록은 다른 통로로 읽는다

1. read 를 9번 파일 기술자로

while read -r -u 9 HOST DIR; do
  some_cli --target "$HOST" --path "$DIR" < /dev/null
done 9< list.txt

이 방식은 안쪽 명령이 표준입력을 읽는지 일일이 알아보지 않아도 된다는 점이 좋습니다. 새 도구를 루프 안에 넣을 때마다 그 도구의 동작을 따로 확인할 필요가 없어집니다.

목록 파일은 9번 통로로 열고 read -u 9 로 거기서만 읽습니다. 안쪽 명령에는 < /dev/null 로 입력을 아예 주지 않습니다. 이렇게 하면 안쪽 명령이 무엇을 하든 목록은 그대로 남습니다.

2. 루프 안의 ssh 에는 -n

while read -r HOST; do
  ssh -n "user@$HOST" "uptime"
done < hosts.txt

-n 은 ssh 의 표준입력을 /dev/null 로 돌립니다. 다만 rsync 의 -e "ssh ..." 안에는 붙이면 안 됩니다. rsync 는 그 ssh 의 표준입력으로 데이터를 주고받기 때문입니다.

전부 처리됐는지 확인하는 법

완료 알림은 스크립트가 맨 끝 줄까지 내려갔다는 뜻일 뿐, 목록을 다 돌았다는 뜻이 아닙니다. 완료 메시지를 믿지 말고 대상 수와 처리 수를 대조합니다. 스크립트 끝에 두 숫자를 같이 찍고, 다르면 실패로 끝나게 만들어 두면 같은 일이 다시 생겨도 바로 드러납니다.

TOTAL=$(grep -c . list.txt)
echo "처리 $DONE / 대상 $TOTAL"
[ "$DONE" -eq "$TOTAL" ] || { echo "개수 불일치"; exit 1; }

"올림 1 / 대상 27" 같은 줄이 이미 찍히고 있었는데도 놓친 것은, 그 줄을 보고 판정하는 장치가 없었기 때문입니다. 숫자를 찍는 데서 끝내지 말고 비교까지 코드로 하는 것이 좋습니다.

왜 하필 첫 줄은 처리되나

첫 번째 read 는 안쪽 명령이 시작되기 전에 실행되므로 첫 줄은 정상으로 읽힙니다. 그다음 안쪽 명령이 시작되면서 남은 줄을 모두 가져가고, 그 명령은 그 내용을 쓰지 않은 채 끝납니다. 두 번째 read 는 파일 끝을 만나 거짓을 돌려주고 루프가 끝납니다.

이 과정 어디에도 오류가 없습니다. 그래서 스크립트는 정상 종료하고, 그 뒤에 붙여 둔 완료 알림까지 그대로 나갑니다. 안쪽 명령을 바꾸거나 새로 추가할 때마다 이 함정이 다시 생길 수 있으므로, 처음부터 9번 통로 방식으로 써 두는 편이 안전합니다.

확인한 환경

항목 내용
셸 bash
루프 안 명령 표준입력을 읽는 CLI, ssh, rsync

이어서 읽을 글

이 글의 내용은 2026년 10월 5일에 마지막으로 확인했습니다. 틀린 곳을 발견하시면 연락처로 알려 주세요.