고급 Bash 스크립팅: 자동화를 위한 셸 기능 마스터하기
배열, 프로세스 대체, 엄격 모드, ShellCheck, 매개변수 확장을 사용한 고급 Bash 스크립팅으로 더 안전한 자동화를 배웁니다.
고급 Bash 스크립팅: 자동화를 위한 셸 기능 마스터하기
Bash 스크립팅은 스크립트가 몇 개의 명령어에서 실제 자동화로 성장할 때 더 어려워집니다. 더 안전한 변수 처리, 더 깔끔한 입력 및 출력, 그리고 더 적은 임시 파일이 필요합니다.
이 가이드는 배포 스크립트, 로그 확인, 유지 관리 작업에서 사용할 수 있는 고급 Bash 스크립팅 기능을 다룹니다. 목표는 영리한 셸 코드가 아닙니다. 재실행, 디버그, 그리고 다른 엔지니어에게 전달할 수 있는 코드입니다.
1. 실제 목록을 위해 Bash 배열 사용하기
배열을 사용하면 공백으로 문자열을 분할하지 않고 여러 값을 저장할 수 있습니다. 이는 파일 이름, 서비스 이름 또는 사용자 입력에 공백이 포함될 때 중요합니다.
인덱스 배열
인덱스 배열은 가장 일반적인 유형으로, 요소는 0부터 시작하는 숫자 인덱스를 사용하여 액세스됩니다.
예시:
# 인덱스 배열 초기화
COLORS=("red" "green" "blue" "yellow")
# 요소 액세스
echo "두 번째 색상은: ${COLORS[1]}"
# 요소 추가
COLORS+=( "purple" )
# 모든 요소 출력
echo "모든 색상: ${COLORS[@]}"
일반적인 배열 연산:
| 연산 | 구문 | 설명 |
|---|---|---|
| 요소 개수 가져오기 | ${#ARRAY[@]} |
총 요소 수를 반환합니다. |
| 특정 요소 길이 가져오기 | ${#ARRAY[index]} |
특정 인덱스에 있는 문자열의 길이를 반환합니다. |
| 반복 | for item in "${ARRAY[@]}" |
모든 요소를 처리하기 위한 표준 루프 구조입니다. |
항상 "${ARRAY[@]}"로 배열 확장을 인용하세요. 그러면 "/var/log/my app.log"가 두 개의 인수가 아닌 하나의 인수로 유지됩니다.
연관 배열
연관 배열은 작은 키-값 맵처럼 작동합니다. Bash 4 이상이 필요하므로, 이전 macOS 호스트를 지원하는 경우 대상 시스템을 확인하세요.
연관 배열은 -A로 선언해야 합니다:
# 연관 배열로 선언
declare -A CONFIG_MAP
# 키-값 쌍 할당
CONFIG_MAP["port"]=8080
CONFIG_MAP["hostname"]="localhost"
CONFIG_MAP["timeout"]=30
# 값 액세스
echo "포트 설정: ${CONFIG_MAP["port"]}"
# 키 반복
for key in "${!CONFIG_MAP[@]}"; do
echo "키: $key, 값: ${CONFIG_MAP[$key]}"
done
2. 임시 파일 대신 프로세스 대체 사용하기
<(command) 또는 >(command)로 작성된 프로세스 대체는 명령어가 다른 명령어의 출력을 파일처럼 처리하도록 합니다. 도구가 파일 경로를 예상하지만 데이터가 명령어에서 오는 경우 유용합니다.
도움이 되는 경우
예를 들어, 두 개의 생성된 서비스 목록을 비교해야 한다고 가정해 보겠습니다. diff는 파일 경로를 예상하지만 해당 목록을 /tmp에 쓸 필요는 없습니다.
프로세스 대체 없이:
# 비효율적이고 지저분함
output1=$(command_a)
echo "$output1" > /tmp/temp1.txt
output2=$(command_b)
echo "$output2" > /tmp/temp2.txt
diff /tmp/temp1.txt /tmp/temp2.txt
rm /tmp/temp1.txt /tmp/temp2.txt
더 깔끔한 직접 비교
프로세스 대체는 diff에 임시 파일 디스크립터를 제공하고 스크립트를 더 간단하게 유지합니다.
프로세스 대체 사용:
# 깔끔한 한 줄 비교
diff <(command_a) <(command_b)
이 패턴은 comm, diff 및 여러 파일 입력이 필요한 도구에서 잘 작동합니다.
구문 변형:
<(command)는 명령어 출력을 리더에 전달합니다.>(command)는 작성된 출력을 다른 명령어로 보냅니다.
3. 엄격 모드 및 ShellCheck 추가하기
고급 Bash 스크립팅은 예상치 못한 일이 발생할 때 큰 소리로 실패해야 합니다. 엄격 모드는 누락된 변수와 손상된 파이프라인이 조용한 손상을 일으키기 전에 포착하는 데 도움이 됩니다.
필수 엄격 모드 옵션
대부분의 자동화 스크립트는 다음으로 시작해야 합니다:
set -euo pipefail
-e: 명령어가 실패하면 종료합니다.-u: 설정되지 않은 변수를 오류로 처리합니다.-o pipefail: 파이프라인의 모든 명령어가 실패하면 파이프라인을 실패시킵니다.
예시:
# pipefail 없이, wc가 깨끗하게 종료되므로 이것은 성공적으로 보일 수 있습니다
cat file.log | grep successful_pattern | wc -l
# set -o pipefail을 사용하면 grep 실패가 파이프라인을 실패시킵니다.
ShellCheck 사용하기
ShellCheck는 인용 실수, 안전하지 않은 확장, 도달할 수 없는 코드 및 일반적인 이식성 문제를 포착합니다.
스크립트를 커밋하기 전에 실행하세요:
shellcheck your_script.sh
ShellCheck가 변수를 인용하거나 "${array[@]}"를 사용하도록 요청하면, 무시할 명확한 이유가 없는 한 실제 버그로 처리하세요.
4. 출력을 신중하게 캡처하기
$()를 사용한 명령어 대체는 유용하지만, 무심코 사용하면 실패를 숨기거나 출력 스트림을 혼합할 수 있습니다.
STDOUT 및 STDERR 모두 캡처하기
명령어에서 모든 것을 기록하려면 표준 출력과 표준 오류를 모두 캡처하세요:
# stdout과 stderr를 모두 VARIABLE에 캡처
VARIABLE=$(command_that_might_fail 2>&1)
# 종료 코드만 필요한 경우 stdout과 stderr를 모두 버림
command_that_might_fail &> /dev/null
인라인 정리를 위한 매개변수 확장
매개변수 확장은 간단한 경우에 sed 또는 awk를 생성하지 않고 문자열을 정리할 수 있습니다.
${variable%pattern}는 가장 짧은 일치 접미사를 제거합니다.${variable%%pattern}는 가장 긴 일치 접미사를 제거합니다.${variable#pattern}는 가장 짧은 일치 접두사를 제거합니다.${variable##pattern}는 가장 긴 일치 접두사를 제거합니다.
예시:
FILE="report.log.bak"
# .bak과 일치하는 가장 짧은 접미사 제거
CLEAN_NAME=${FILE%.bak}
echo $CLEAN_NAME # 출력: report.log
# *.bak과 일치하는 모든 접미사 제거 (여기서는 .bak만 제거)
CLEAN_NAME_LONG=${FILE%%.*}
echo $CLEAN_NAME_LONG # 출력: report
도움을 받아야 할 때
스크립트가 파일을 삭제하거나, 프로덕션 서비스를 변경하거나, 비밀을 처리하거나, CI에서 실행될 때 더 경험 많은 셸 사용자에게 검토를 요청하세요. Bash는 강력하지만, 작은 인용 실수도 패턴과 일치하는 모든 파일에 영향을 미칠 수 있습니다.
결론
실제 목록에는 배열을, 파일과 유사한 명령어 출력에는 프로세스 대체를, 더 안전한 실패에는 set -euo pipefail을, 빠른 피드백에는 ShellCheck를 사용하세요. 이러한 습관은 고급 Bash 스크립팅을 유지 관리하기 쉽게 만들고 자동화 실행 중에 훨씬 덜 놀라게 만듭니다.