최대 성능을 위한 10가지 필수 Bash 스크립팅 팁
프로세스 포크를 줄이고, 내장 명령어를 사용하며, 파일 작업을 배치 처리하고, 적절한 텍스트 도구를 선택하여 Bash 스크립트 속도를 높이세요.
최대 성능을 위한 10가지 필수 Bash 스크립팅 팁
Bash 스크립팅 성능은 일반적으로 하나의 문제로 귀결됩니다: 쉘이 한 번에 하나의 프로세스에 얼마나 많은 작업을 요청하느냐입니다. 10개의 파일에는 괜찮아 보이는 스크립트가 5만 개의 파일을 반복하면서 항목당 sed, grep, 또는 chmod를 실행하기 시작하면 고통스러울 정도로 느려질 수 있습니다.
스크립트가 많은 파일을 처리하거나, 큰 텍스트 출력을 파싱하거나, 작은 지연이 누적될 정도로 자주 실행될 때 이 팁들을 사용하세요.
1. 외부 명령어 호출 최소화
Bash가 외부 명령어(예: grep, awk, sed)를 실행할 때마다 새로운 프로세스를 포크(fork)하며, 이는 상당한 오버헤드를 발생시킵니다. 스크립트 속도를 높이는 가장 효과적인 방법은 가능할 때마다 Bash 내장 명령어를 활용하는 것입니다.
외부 유틸리티보다 내장 명령어 선호
예시: 조건부 검사에 외부 test 또는 [ 대신:
| 느림 (외부) | 빠름 (내장) |
|---|---|
if test -f "$FILE"; then |
if [[ -f "$FILE" ]]; then |
팁: 산술 연산의 경우 expr이나 let 대신 항상 (( ... ))를 사용하세요. 산술 확장은 쉘 내부에서 처리되기 때문입니다.
# 느림
COUNT=$(expr $COUNT + 1)
# 빠름 (내장 산술 확장)
(( COUNT++ ))
2. 효율적인 반복 구조 사용
명령어 출력을 반복하는 전통적인 for 루프는 프로세스 생성이나 단어 분리 문제로 인해 느릴 수 있습니다. 네이티브 중괄호 확장이나 while read 루프를 올바르게 사용하세요.
for i in $(cat file) 사용 피하기
$(cat file)을 사용하면 전체 파일을 먼저 메모리로 읽어 들인 후 단어 분리를 수행하므로 비효율적이며 파일 이름에 공백이 있으면 오류가 발생하기 쉽습니다. 줄 단위 처리를 위해 while read 루프를 대신 사용하세요:
# 파일을 줄 단위로 처리하는 권장 방법
while IFS= read -r line;
do
echo "처리 중: $line"
done < "data.txt"
IFS= read -r 참고: IFS=를 설정하면 앞뒤 공백 제거를 방지하고, -r은 백슬래시 해석을 방지하여 데이터 무결성을 보장합니다.
3. 매개변수 확장으로 데이터 내부 처리
Bash는 문자열에서 접두사/접미사 제거, 치환, 대소문자 변환과 같은 강력한 매개변수 확장 기능을 제공하며, 이는 간단한 작업을 위해 sed나 awk 같은 외부 도구를 사용하지 않고 내부적으로 문자열을 처리합니다.
예시: 접두사 제거
변수 filename에서 접두사 log_를 제거해야 하는 경우:
filename="log_report_2023.txt"
# 느림 (외부 sed)
# new_name=$(echo "$filename" | sed 's/^log_//')
# 빠름 (내장 확장)
new_name=${filename#log_}
echo "$new_name" # 출력: report_2023.txt
4. 비용이 많이 드는 명령어 출력 캐싱
스크립트 내에서 동일한 비용이 많이 드는 명령어(예: API 호출, 복잡한 파일 검색)를 여러 번 실행하는 경우, 결과를 변수나 임시 파일에 캐싱하여 반복 실행을 피하세요.
# 시작 시 한 번만 실행
GLOBAL_CONFIG=$(get_system_config_from_db)
# 이후 사용 시 변수를 직접 읽음
if [[ "$GLOBAL_CONFIG" == *"DEBUG_MODE"* ]]; then
echo "디버그 모드 활성화."
fi
5. 리스트에 배열 변수 사용
항목 목록을 다룰 때는 공백으로 구분된 문자열 대신 Bash 배열을 사용하세요. 배열은 공백이 포함된 항목을 올바르게 처리하며 일반적으로 반복 및 조작에 더 효율적입니다.
# 느리고 오류가 발생하기 쉬운 문자열 리스트
# FILES="file A fileB.txt"
# 빠르고 강력한 배열
FILES_ARRAY=( "file A" "fileB.txt" "another file" )
# 효율적인 반복
for f in "${FILES_ARRAY[@]}"; do
process_file "$f"
done
6. 과도한 따옴표 사용 및 해제 피하기
올바른 따옴표 사용은 정확성(특히 공백이 있는 파일 이름 처리 시)에 중요하지만, 과도한 따옴표 사용과 해제는 약간의 오버헤드를 추가할 수 있습니다. 더 중요한 것은 따옴표가 필수적인 경우와 선택적인 경우를 이해하는 것입니다.
산술 확장 ((...))의 경우, 명령어 치환 $()와 달리 표현식 자체에는 일반적으로 따옴표가 필요하지 않습니다.
7. 가능한 경우 프로세스 치환을 파이프라인에 사용
프로세스 치환(<(cmd))은 특히 한 명령어의 출력을 동시에 다른 명령어의 두 부분에 전달해야 할 때 네임드 파이프(mkfifo)보다 더 깔끔하고 빠른 파이프라인을 만들 수 있습니다.
# 정렬된 두 파일의 내용을 효율적으로 비교
if cmp <(sort file1.txt) <(sort file2.txt); then
echo "정렬 시 파일이 동일합니다."
fi
8. echo 대신 printf 사용
종종 무시할 만하지만, echo의 동작은 쉘과 시스템에 따라 달라질 수 있으며, 백슬래시 해석을 위해 더 복잡한 처리가 필요할 수 있습니다. printf는 일관된 형식과 뛰어난 제어를 제공하므로 일반적으로 더 안정적이며 대량 출력 작업에서 약간 더 빠를 수 있습니다.
# 일관된 출력
printf "사용자 %s가 %s에 로그인했습니다\n" "$USER" "$(date +%T)"
9. -exec ... {} ;보다 find ... -exec ... {} + 선호
find 명령어를 사용하여 발견된 파일에 대해 다른 프로그램을 실행할 때, 세미콜론(;)으로 종료하는 것과 플러스 기호(+)로 종료하는 것의 성능 차이는 엄청납니다.
{}; 명령어를 파일당 한 번 실행합니다. (높은 오버헤드){}+ 가능한 많은 인수를 묶어 명령어를 한 번 실행합니다(xargs처럼). (낮은 오버헤드)
# 느림: 'chmod 644'를 수천 번 실행
find . -name '*.txt' -exec chmod 644 {} \;
# 빠름: 'chmod 644'를 많은 인수와 함께 한 번 또는 몇 번 실행
find . -name '*.txt' -exec chmod 644 {} +
10. 대량 텍스트 처리에는 awk 또는 perl 사용
외부 호출을 최소화하는 것이 목표이지만, 무겁고 복잡한 텍스트 조작이 필요한 경우, awk나 perl 같은 특화된 도구가 여러 grep, sed, cut 명령어를 연결하는 것보다 훨씬 빠릅니다. 이러한 도구는 데이터를 단일 패스로 처리합니다.
cat file | grep X | sed Y | awk Z와 같은 코드를 작성하고 있다면, 이를 단일하고 최적화된 awk 스크립트로 통합하세요.
실용적인 규칙
빠른 Bash 스크립트는 Bash 루프 내에서 더 적은 작업을 수행합니다. 간단한 테스트와 문자열 편집에는 쉘 내장 명령어를 사용하고, find -exec ... {} + 또는 xargs로 파일 작업을 배치 처리하며, 텍스트 처리가 주요 작업이 될 때는 awk, perl 또는 다른 실제 파서로 전환하세요.
최적화하기 전에 time 또는 쉘 추적으로 대표적인 실행을 측정하세요. 그런 다음 가장 많은 명령어를 생성하는 루프를 수정하세요.