강력한 반복 전략: Bash 스크립트에서 파일과 리스트 순회하기
`for`와 `while`을 사용한 필수 Bash 반복 기법을 마스터하여 반복적인 시스템 작업을 효율적으로 자동화하세요. 이 포괄적인 가이드는 리스트 순회, 숫자 시퀀스 처리, 그리고 `while IFS= read -r`과 같은 모범 사례를 사용한 파일의 견고한 줄 단위 처리를 다룹니다. 기본 문법, 고급 루프 제어(`break`, `continue`), 그리고 강력하고 신뢰할 수 있는 셸 스크립팅 및 자동화를 위한 필수 기술을 실용적인 코드 예제와 함께 배우세요.
강력한 반복 전략: Bash 스크립트에서 파일과 리스트 순회하기
Bash 반복문은 작은 셸 명령어를 유용한 자동화 도구로 바꿔줍니다. 디렉토리의 모든 파일을 처리해야 하거나, 정해진 횟수만큼 작업을 수행해야 하거나, 설정 데이터를 한 줄씩 읽어야 하는 경우, 반복문은 명령어를 복사-붙여넣기 하지 않고도 작업을 반복할 수 있는 구조를 제공합니다.
가장 많이 사용하게 될 두 가지 반복문은 for와 while입니다. 배열이나 파일 글로브(glob)와 같이 이미 알려진 항목 집합이 있을 때는 for를 사용하세요. 반복문이 조건이나 입력 읽기에 의해 구동될 때는 while을 사용하세요. 이 간단한 구분만으로도 많은 스크립트를 더 쉽게 이해할 수 있습니다.
for 반복문: 고정된 집합 순회하기
for 반복문은 미리 처리해야 할 항목의 모음을 알고 있을 때 이상적입니다. 이 모음은 명시적인 값의 리스트, 명령어의 결과, 또는 글로빙(globbing)을 통해 찾은 파일 집합이 될 수 있습니다.
1. 표준 리스트 순회하기
가장 간단한 사용법은 스크립트에 직접 작성된 짧은 단어 리스트를 순회하는 것입니다.
문법
for 변수 in 항목_리스트; do
# $변수를 사용하는 명령어들
done
예제: 사용자 리스트 처리하기
# 처리할 사용자 리스트
USERS="alice bob charlie"
for user in $USERS; do
echo "$user의 홈 디렉토리를 확인하는 중..."
if [ -d "/home/$user" ]; then
echo "$user 사용자가 활성 상태입니다."
else
echo "경고: $user 사용자의 홈 디렉토리가 없습니다."
fi
done
이 패턴은 간단한 이름에 적합합니다. 항목에 공백이 포함될 수 있다면 공백으로 구분된 문자열 대신 배열을 사용하세요:
USERS=("alice" "bob" "mary jane")
for user in "${USERS[@]}"; do
echo "$user 확인 중"
done
2. C 스타일 숫자 반복
카운팅이나 특정 숫자 시퀀스가 필요한 작업의 경우, Bash는 C 스타일 for 반복문을 지원하며, 종종 중괄호 확장(brace expansion)이나 seq 명령어와 함께 사용됩니다.
문법 (C 스타일)
for (( 초기화; 조건; 증가 )); do
# 명령어들
done
예제: 카운트다운 스크립트
# 5번 반복 (i는 1에서 시작, i가 5보다 작거나 같을 동안 계속)
for (( i=1; i<=5; i++ )); do
echo "반복 횟수: $i"
sleep 1
done
echo "완료!"
대안: 간단한 시퀀스를 위한 중괄호 확장 사용
중괄호 확장은 연속된 정수나 시퀀스를 생성할 때 seq를 사용하는 것보다 더 간단하고 빠릅니다.
# 10부터 1까지 숫자 생성
for num in {10..1}; do
echo "카운트다운: $num"
done
3. 파일 및 디렉토리 순회하기 (글로빙)
for 반복문 내에서 와일드카드(*)를 사용하면 특정 패턴(예: 모든 로그 파일 또는 디렉토리의 모든 스크립트)과 일치하는 파일을 처리할 수 있습니다.
예제: 로그 파일 아카이빙
특히 공백이나 특수 문자가 포함된 파일 이름을 다룰 때는 변수("$file")를 인용 부호로 감싸세요.
TARGET_DIR="/var/log/application"
# 대상 디렉토리에서 .log로 끝나는 모든 파일을 순회
for logfile in "$TARGET_DIR"/*.log; do
# 파일이 실제로 존재하는지 확인 (일치하는 파일이 없을 때 리터럴 "*.log"에서 실행되는 것을 방지)
if [ -f "$logfile" ]; then
echo "$logfile 압축 중..."
gzip "$logfile"
fi
done
while 반복문: 조건 기반 실행
while 반복문은 지정된 조건이 참인 동안 명령어 블록을 계속 실행합니다. 입력 스트림 읽기, 조건 모니터링, 또는 반복 횟수를 알 수 없는 작업을 처리하는 데 일반적으로 사용됩니다.
1. 기본 while 반복문
문법
while 조건; do
# 명령어들
done
예제: 리소스 대기
이 반복문은 test 명령어([ ])를 사용하여 디렉토리가 존재하는지 확인한 후 진행합니다.
RESOURCE_PATH="/mnt/data/share"
while [ ! -d "$RESOURCE_PATH" ]; do
echo "리소스 $RESOURCE_PATH가 마운트되기를 기다리는 중..."
sleep 5
done
echo "리소스를 사용할 수 있습니다. 백업을 시작합니다."
2. 강력한 while read 패턴
while 반복문의 가장 강력한 응용은 파일 내용이나 출력 스트림을 한 줄씩 읽는 것입니다. 이 패턴은 cat의 출력에 for 반복문을 사용하는 것보다 훨씬 뛰어나며, 공백과 특수 문자를 안정적으로 처리합니다.
모범 사례: 줄 단위 읽기
최대한의 견고함을 보장하기 위해 세 가지 핵심 구성 요소를 활용합니다:
IFS=: 내부 필드 구분자(Internal Field Separator)를 비워서 선행/후행 공백을 포함한 전체 줄이 변수로 읽히도록 합니다.read -r:-r옵션은 백슬래시 해석을 방지(원시 읽기)하여 경로 및 복잡한 문자열에 중요합니다.- 입력 재지정 (
<): 파일 내용을 반복문 안으로 리디렉션하여 반복문이 현재 셸 컨텍스트에서 실행되도록 합니다(서브셸 문제 방지).
# 데이터를 포함한 파일, 한 줄에 하나의 항목
CONFIG_FILE="/etc/app/servers.txt"
while IFS= read -r server_name; do
# 빈 줄이나 주석 줄 건너뛰기
if [[ -z "$server_name" || "$server_name" =~ ^# ]]; then
continue
fi
echo "서버 핑: $server_name"
ping -c 1 "$server_name"
done < "$CONFIG_FILE"
팁: 반복문에서
cat사용 피하기파일을 읽을 때는
cat file | while ...보다while ... done < file을 선호하세요. 대부분의 Bash 구성에서 파이프라인은 서브셸에서 반복문을 실행하므로, 반복문 내부에서 변경된 변수는 반복문이 종료되면 손실됩니다.
3. find에서 파일 이름 처리하기
재귀적 파일 처리를 위해 find의 일반 출력을 한 줄씩 파싱하는 것은 피하세요. 파일 이름에는 공백과 드물게 개행 문자가 포함될 수 있습니다. 널(null)로 구분된 출력을 사용하세요:
find /var/log/application -type f -name '*.log' -print0 |
while IFS= read -r -d '' logfile; do
echo "로그 발견: $logfile"
gzip -- "$logfile"
done
-print0과 read -d '' 쌍은 널 바이트를 구분자로 처리합니다. "$logfile" 앞의 --는 gzip에게 다음 값들이 피연산자(operand)이지 옵션이 아님을 알려주어, -로 시작하는 파일 이름으로부터 보호합니다.
고급 반복 제어 및 기술
효과적인 스크립트는 런타임 조건에 따라 반복 실행을 제어하는 기능을 필요로 합니다.
1. 흐름 제어: break와 continue
break: 남은 반복 횟수나 조건에 관계없이 전체 반복문을 즉시 종료합니다.continue: 현재 반복을 건너뛰고 즉시 다음 반복으로 이동합니다(또는while조건을 다시 평가합니다).
예제: 검색 및 중지
SEARCH_TARGET="target.conf"
for file in /etc/*; do
if [ -f "$file" ] && [[ "$file" == *"$SEARCH_TARGET"* ]]; then
echo "대상 설정 파일을 찾았습니다: $file"
break # 찾으면 처리를 중지
elif [ -d "$file" ]; then
continue # 디렉토리는 건너뛰고 파일만 확인
fi
echo "파일 확인 중: $file"
done
2. IFS를 사용한 복잡한 구분자 처리
파일을 줄 단위로 읽을 때는 IFS를 비워야 하지만, 다른 문자(예: 쉼표)로 구분된 리스트를 순회할 때는 일시적으로 IFS를 설정해야 합니다.
CSV_DATA="data1,data2,data3,data4"
OLD_IFS=$IFS # 원래 IFS 저장
IFS=',' # IFS를 쉼표 문자로 설정
for item in $CSV_DATA; do
echo "항목 발견: $item"
done
IFS=$OLD_IFS # 반복문 직후 원래 IFS 복원
경고: 전역
IFS변경스크립트 내에서
$IFS를 수정하기 전에 항상 원래 값을 저장하세요(예:OLD_IFS=$IFS). 원래 값을 복원하지 않으면 이후 명령어에서 예측할 수 없는 동작이 발생할 수 있습니다.
강력한 Bash 반복문을 위한 모범 사례
| 사례 | 이유 |
|---|---|
| 항상 변수 인용 | "$variable"을 사용하여 단어 분리(word splitting)와 글로브 확장(glob expansion)을 방지합니다. 특히 파일 순회 시 중요합니다. |
while IFS= read -r 사용 |
파일을 줄 단위로 처리하는 가장 신뢰할 수 있는 방법으로, 공백과 특수 문자를 올바르게 처리합니다. |
| 존재 여부 확인 | 글로빙(*.txt)을 사용할 때는 항상 확인(if [ -f "$file" ];)을 포함하여 일치하는 파일이 없을 경우 반복문이 리터럴 패턴 이름을 처리하지 않도록 합니다. |
| 변수 지역화 | 함수 내에서 local 키워드를 사용하여 반복 변수가 실수로 전역 변수를 덮어쓰는 것을 방지합니다. |
| 외부 명령어보다 내장 명령어 사용 | 성능을 위해 seq와 같은 외부 명령어를 생성하는 대신 중괄호 확장({1..10}) 또는 C 스타일 반복문을 사용하세요. |
실용적인 경험 법칙
메모리 내 리스트에는 배열을, 간단한 파일 집합에는 글로브를, 줄 지향 입력에는 while IFS= read -r을, 재귀적 파일 이름 처리에는 널로 구분된 find 출력을 사용하세요. 기본적으로 확장을 인용하세요. 글로브 주변에 존재 여부 확인을 추가하세요. 반복문을 더 읽기 쉽게 만드는 경우에만 break와 continue를 사용하고, 복잡한 제어 흐름을 숨기는 방법으로 사용하지 마세요.
대부분의 Bash 반복문 버그는 단어 분리, 예상치 못한 파일 이름, 또는 입력이 실제보다 더 깔끔하다고 가정하는 데서 발생합니다. 반복문이 공백, 빈 줄, 주석, 일치하지 않는 항목을 의도적으로 처리한다면 실제 자동화 작업에서도 잘 작동할 것입니다.