강력한 스크립트를 위한 Bash 인수 파싱 마스터하기

위치 매개변수, getopts, 긴 옵션 루프, 기본값 및 명확한 사용법 오류를 사용하여 Bash 인수를 파싱합니다.

강력한 스크립트를 위한 Bash 인수 파싱 마스터하기

명령줄 인수는 Bash 스크립트를 유연하게 만듭니다. Bash 인수 파싱이 약하면 다른 파일, 호스트 또는 모드가 필요할 때마다 스크립트 내부의 변수를 편집해야 합니다.

이 가이드는 위치 인수, getopts를 사용한 짧은 옵션, while/case 루프를 사용한 긴 옵션을 처리하는 방법을 보여줍니다. 목표는 잘못된 입력을 조기에 거부하고 정확한 실행 방법을 알려주는 스크립트를 만드는 것입니다.

기본: 위치 인수

플래그와 명명된 옵션을 다루기 전에 Bash가 간단한 순차적 인수를 처리하는 방식을 이해하는 것이 중요합니다.

변수 설명 ./script.sh foo bar로 호출된 경우 예시
$0 스크립트 자체의 이름 ./script.sh
$1, $2, ... 첫 번째, 두 번째 등 위치 인수 foo, bar
$# 위치 인수의 개수 2
$@ 모든 위치 인수, 개별 문자열로 처리됨 foo bar
$* 모든 위치 인수, 단일 문자열로 처리됨 foo bar

예시: 간단한 위치 확인

#!/bin/bash

if [ "$#" -ne 2 ]; then
    echo "사용법: $0 <소스_파일> <대상_디렉토리>"
    exit 1
fi

SOURCE="$1"
DESTINATION="$2"

echo "$SOURCE를 $DESTINATION으로 복사 중..."
# cp "$SOURCE" "$DESTINATION"

짧은 옵션을 위한 getopts를 사용한 표준 파싱

-v(상세) 또는 -f <파일>과 같은 옵션이 필요한 전문 스크립트의 경우, 내장된 getopts 유틸리티가 순수 Bash에서 가장 표준적이고 신뢰할 수 있는 방법입니다. 이는 짧은(단일 문자) 옵션을 위해 특별히 설계되었습니다.

getopts 작동 방식

getopts는 옵션을 순차적으로 읽어 지정된 변수(보통 OPT)를 발견된 옵션으로 설정하고, 필요한 경우 인수의 값을 OPTARG 변수에 넣습니다.

  • 옵션 문자열(OPTSTRING): 유효한 옵션을 정의합니다. 옵션에 인수가 필요한 경우(예: -f 파일명), 문자 뒤에 콜론(:)을 붙입니다. 예: vho:-v, -h, 그리고 값을 필요로 하는 -o를 허용합니다.
  • OPTIND: 다음에 처리할 인수를 추적하는 내부 Bash 인덱스입니다. 반드시 1로 초기화해야 합니다(기본값이지만 복잡한 스크립트에서는 재설정되는 경우가 있음).

실용적인 getopts 템플릿

이 템플릿은 세 가지 옵션을 처리합니다: -v(플래그), -h(플래그), -f(값이 필요한 옵션).

#!/bin/bash

# --- 기본값 ---
VERBOSE=0
FILENAME="default.txt"

# --- 사용법 함수 ---
usage() {
    echo "사용법: $0 [-v] [-h] [-f <파일>] <입력>"
    exit 1
}

# --- 인수 파싱 루프 ---
while getopts ":vhf:" OPT; do
    case "$OPT" in
        v)
            VERBOSE=1
            echo "상세 모드 활성화됨."
            ;;
        h)
            usage
            ;;
        f)
            # OPTARG는 -f에 제공된 인수를 보유
            FILENAME="$OPTARG"
            echo "출력 파일명 설정됨: $FILENAME"
            ;;
        \?)
            # 인식되지 않은 옵션 처리
            echo "오류: 잘못된 옵션 -$OPTARG" >&2
            usage
            ;;
        :)
            # 인수가 필요한 옵션에 인수가 누락된 경우 처리 (예: 파일명 없이 -f)
            echo "오류: 옵션 -$OPTARG에는 인수가 필요합니다." >&2
            usage
            ;;
    esac
done

# --- 위치 인수 이동 ---
# getopts가 완료된 후, OPTIND는 첫 번째 비옵션 인수의 인덱스를 보유합니다.
# shift를 사용하여 파싱된 모든 옵션을 버리고 위치 인수($1, $2 등)만 남깁니다.
shift $((OPTIND - 1))

# 필수 위치 인수(INPUT)가 있는지 확인
INPUT_DATA="$1"
if [ -z "$INPUT_DATA" ]; then
    echo "오류: 입력 데이터가 필요합니다."
    usage
fi

echo "---"
echo "입력 처리 중: $INPUT_DATA"
echo "상세 상태: $VERBOSE"

팁: while getopts 루프 직후에 항상 shift $((OPTIND - 1))를 사용하여 옵션을 나머지 위치 인수와 깔끔하게 분리하세요.

--option과 같은 긴 옵션 처리

순수 Bash의 내장 getopts는 짧은 옵션만 지원합니다. 현대적인 긴 옵션(예: --verbose, --output-file=data.log)을 처리하려면 shift 명령과 함께 whilecase를 사용하여 사용자 정의 파싱 루프를 구현해야 합니다.

이 방법은 더 명시적인 인수 관리가 필요합니다.

사용자 정의 긴 옵션 파서

#!/bin/bash

# --- 기본값 ---
VERBOSE=0
OUTPUT_FILE=""

# 사용자 정의 사용법 표시 함수 (간결성을 위해 생략)
# usage() { ... }

while [ "$#" -gt 0 ]; do
    case "$1" in
        --verbose)
            VERBOSE=1
            shift
            ;;
        --output-file)
            # 두 번의 shift 필요: 플래그용과 값용
            if [ -z "${2:-}" ]; then
                echo "오류: --output-file에는 값이 필요합니다."
                exit 1
            fi
            OUTPUT_FILE="$2"
            shift 2
            ;;
        --help)
            usage
            ;;
        -*)
            echo "오류: 알 수 없는 옵션 $1" >&2
            exit 1
            ;;
        *)
            # 첫 번째 위치 인수를 찾음, 옵션 파싱 중단
            break
            ;;
    esac
done

# 나머지 인수는 $1, $2 등으로 사용 가능
# ... 스크립트 로직 계속 ...

if [ "$OUTPUT_FILE" ]; then
    echo "데이터가 $OUTPUT_FILE로 리디렉션됨"
fi

스크립트가 옵션 뒤에 위치 인수를 허용하는 경우, -- 또는 첫 번째 비옵션 인수를 만날 때까지 파싱을 계속하세요. 일반적인 관례는 --가 "지금부터 옵션 파싱 중단"을 의미하도록 하는 것입니다:

while [ "$#" -gt 0 ]; do
    case "$1" in
        --)
            shift
            break
            ;;
        --verbose)
            VERBOSE=1
            shift
            ;;
        *)
            break
            ;;
    esac
done

고급: 키-값 긴 옵션 처리 (--key=value)

등호를 사용하여 인수가 전달되는 표준 UNIX 스타일을 선호하는 경우, 매개변수 대체를 사용하여 인수를 분할해야 합니다.

while [ "$#" -gt 0 ]; do
    case "$1" in
        --limit=*)
            LIMIT_VAL="${1#*=}" # '='를 포함한 모든 항목 제거
            echo "제한값 설정됨: $LIMIT_VAL"
            shift
            ;;
        # ... 다른 옵션 ...
    esac
done

강력한 스크립팅을 위한 모범 사례

A. 짧은 옵션과 긴 옵션 결합

최대 유연성을 위해 숙련된 스크립터는 종종 짧은 옵션에 getopts의 신뢰성과 긴 옵션에 사용자 정의 while/case 루프를 결합합니다. 긴 옵션 루프가 먼저 실행되어 긴 플래그를 소비한 다음, 나머지 인수(짧은 옵션 포함)가 getopts에 의해 처리됩니다.

그러나 더 깔끔하고 일반적인 패턴은 -o--option을 모두 찾고 그에 따라 이동하는 하나의 강력한 사용자 정의 루프에서 모든 인수를 처리하는 것입니다.

B. 오류 처리 및 사용법

스크립트의 필수 인수와 옵션을 설명하는 명확한 usage 함수를 항상 제공하세요. 이 함수는 다음 경우에 호출되어야 합니다:

  1. 사용자가 도움말을 요청할 때(예: -h 또는 --help).
  2. 필수 인수가 누락된 경우.
  3. 잘못되었거나 알 수 없는 옵션이 제공된 경우.

오류 발생 시 스크립트가 0이 아닌 상태로 종료되도록(exit 1) 하고, 오류 메시지를 표준 오류(>&2)로 출력하세요.

C. 기본값

모든 구성 변수를 스크립트 시작 부분에 합리적인 기본값으로 초기화하세요. 이렇게 하면 선택적 인수가 전달되지 않더라도 스크립트를 예측 가능하게 만듭니다.

# 변수는 항상 파싱 전에 초기화
LOG_LEVEL="info"
FORCE=0

핵심 요점

간단한 필수 값에는 위치 인수를, 이식 가능한 짧은 옵션에는 getopts를, 긴 옵션이 필요할 때는 사용자 정의 while/case 루프를 사용하세요. 자동화에서 스크립트를 신뢰하기 전에 누락된 값, 알 수 없는 플래그 및 공백이 있는 인수를 테스트하세요.