0Pricing
DevOps Bootcamp · 강의

재사용 가능한 Bash 라이브러리 구축과 불러오기

공유 도우미를 포함 방지 기능과 네임스페이스가 지정된 함수 접두사를 갖춘 불러올 수 있는 .sh 라이브러리 파일로 구성하는 방법을 배웁니다.

재사용 가능한 Bash 라이브러리 구축과 불러오기은(는) CoddyKit의 무료 DevOps Bootcamp 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 DevOps Bootcamp 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. DevOps Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.

Bash 라이브러리란 무엇인가요

소프트웨어 공학에서 라이브러리는 여러 프로그램이 공유할 수 있는 재사용 가능한 함수 모음입니다. Bash에서는 불러올 수 있는 셸 스크립트를 통해 같은 개념을 지원합니다.

모든 스크립트에 도우미 함수를 복사해 붙여넣는 대신, 전용 .sh 파일에 함수를 작성하고 source 명령(또는 줄임말 .)으로 불러옵니다. 해당 파일에 정의된 함수, 변수 또는 별칭은 호출하는 스크립트의 현재 셸 세션에서 사용할 수 있게 됩니다.

  • DRY 원칙(반복하지 않기)을 실천합니다.
  • 버그 수정을 중앙화하여 한 번 수정하면 모든 호출자가 혜택을 받습니다.
  • 개별 스크립트가 더 짧고 읽기 쉬워집니다.
  • 로깅, 오류 처리, 유틸리티 로직의 팀 전체 일관성을 높입니다.

잘 구성된 Bash 프로젝트에는 일반적으로 이러한 공유 파일을 담은 lib/ 디렉터리가 있으며, 이는 고급 언어의 관례와 비슷합니다.

source 명령과 점 연산자

라이브러리 파일을 현재 셸 환경에 불러오는 방법은 서로 같은 두 가지가 있습니다.

  • source /path/to/lib.sh — 명시적이고 읽기 쉬운 형식
  • . /path/to/lib.sh — POSIX 호환 축약 형식

두 방법 모두 하위 셸이 아니라 현재 셸 프로세스에서 파일을 실행하므로, 파일 안에 정의된 모든 함수와 변수는 호출 직후 스크립트 환경의 일부가 됩니다.

일반적으로 $BASH_SOURCE를 사용하여 호출하는 스크립트를 기준으로 라이브러리의 위치를 찾습니다. 이렇게 하면 프로젝트가 어디에 설치되어 있든 이식성을 유지할 수 있습니다.

#!/usr/bin/env bash
# main.sh — load a library relative to this script's own location

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/lib/utils.sh"

echo "Library loaded. Calling greet..."
greet "World"

첫 라이브러리 파일 만들기

라이브러리 파일은 함수 정의(그리고 경우에 따라 상수)만 포함하는 일반적인 .sh 파일입니다. 직접 실행하는 것이 아니라 불러와서 사용해야 하므로 최상위 수준에서 부수 효과가 있는 코드를 실행해서는 안 됩니다.

주요 관례는 다음과 같습니다.

  • 라이브러리의 목적을 설명하는 셔뱅 주석으로 시작합니다.
  • 함수만 정의하고 최상위 수준에 main 로직을 작성하지 않습니다.
  • 함수 안에서는 return을 사용합니다(호출자를 종료시키는 exit는 절대 사용하지 않습니다).
  • 프로젝트의 lib/ 하위 디렉터리에 파일을 둡니다.
#!/usr/bin/env bash
# lib/utils.sh — General-purpose utility functions

# Print a greeting message
greet() {
    local name="${1:-stranger}"
    echo "Hello, ${name}!"
}

# Print a timestamped log line to stderr
log_info() {
    echo "[INFO]  $(date '+%Y-%m-%d %H:%M:%S')  $*" >&2
}

# Print an error message and return a failure code
log_error() {
    echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S')  $*" >&2
    return 1
}

포함 가드: 중복 불러오기 방지

여러 스크립트가 같은 라이브러리를 불러오거나, 라이브러리가 주 스크립트에서도 불러오는 다른 라이브러리를 불러오면 함수가 여러 번 정의될 수 있습니다. 이는 시간을 낭비하며, 실행 중 함수 본문이 바뀌면 알아채기 어려운 오류를 일으킬 수 있습니다.

해결 방법은 플래그 역할을 하는 변수인 포함 가드입니다. 처음 불러올 때는 변수가 설정되지 않았으므로 파일이 계속 실행됩니다. 이후 불러올 때마다 가드가 이미 설정되어 있으므로 파일은 즉시 반환합니다.

이는 C/C++의 #pragma once나 다른 언어의 if not already imported 패턴에 해당하는 Bash 방식입니다.

#!/usr/bin/env bash
# lib/utils.sh — with include guard

# Guard: if already sourced, do nothing
[[ -n "${_LIB_UTILS_LOADED:-}" ]] && return 0
_LIB_UTILS_LOADED=1

greet() {
    local name="${1:-stranger}"
    echo "Hello, ${name}!"
}

log_info() {
    echo "[INFO]  $(date '+%Y-%m-%d %H:%M:%S')  $*" >&2
}

log_error() {
    echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S')  $*" >&2
    return 1
}

네임스페이스 함수 접두사

Bash에는 함수에 대한 단일 전역 네임스페이스가 있습니다. 두 라이브러리가 각각 log나 init이라는 함수를 정의하면 나중의 정의가 앞의 정의를 조용히 덮어씁니다.

표준적인 방어 방법은 네임스페이스 접두사입니다. 라이브러리의 모든 함수 앞에 라이브러리의 짧은 이름과 두 개의 콜론(::) 또는 밑줄을 붙입니다. 예를 들어 문자열 유틸리티 라이브러리는 str::을 사용하고, 파일 라이브러리는 file::을 사용합니다.

  • 이름 충돌 가능성이 크게 줄어듭니다.
  • 호출 코드만 보아도 역할을 알 수 있습니다. str::trim은 함수가 어디에 있는지 정확히 알려 줍니다.
  • 검색이 쉬워집니다. grep 'str::' main.sh을 실행하면 모든 문자열 라이브러리 호출을 즉시 확인할 수 있습니다.
#!/usr/bin/env bash
# lib/str.sh — String utility library (namespaced)

[[ -n "${_LIB_STR_LOADED:-}" ]] && return 0
_LIB_STR_LOADED=1

# Trim leading and trailing whitespace
str::trim() {
    local s="$1"
    s="${s#"${s%%[![:space:]]*}"}"
    s="${s%"${s##*[![:space:]]}"}"  
    echo "$s"
}

# Convert string to uppercase
str::upper() {
    echo "${1^^}"
}

# Convert string to lowercase
str::lower() {
    echo "${1,,}"
}

# Check if a string contains a substring
str::contains() {
    [[ "$1" == *"$2"* ]]
}

lib/ 디렉터리 구성하기

프로젝트가 커지면 하나의 utils.sh 파일로는 관리하기 어려워집니다. lib/ 디렉터리 안에서 각 역할에 집중하는 라이브러리 파일로 나누십시오.

  • lib/log.sh — 로깅 도우미(log::info, log::warn, log::error)
  • lib/str.sh — 문자열 조작(str::trim, str::upper)
  • lib/fs.sh — 파일 시스템 도우미(fs::require_dir, fs::safe_rm)
  • lib/net.sh — 네트워크 확인(net::wait_for_port, net::is_online)

하나의 부트스트랩 로더 파일(lib/bootstrap.sh)에서 모든 파일을 올바른 순서로 불러오게 하면, 각 스크립트에서는 한 번만 불러오면 됩니다.

#!/usr/bin/env bash
# lib/bootstrap.sh — Load all project libraries in dependency order

[[ -n "${_LIB_BOOTSTRAP_LOADED:-}" ]] && return 0
_LIB_BOOTSTRAP_LOADED=1

_BOOTSTRAP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

source "${_BOOTSTRAP_DIR}/log.sh"
source "${_BOOTSTRAP_DIR}/str.sh"
source "${_BOOTSTRAP_DIR}/fs.sh"
source "${_BOOTSTRAP_DIR}/net.sh"

log::info "All libraries loaded."

완전한 로그 기록 라이브러리

로그 기록은 스크립트 전반에서 가장 공통적으로 공유되는 관심사입니다. 전용 lib/log.sh는 출력 형식, 로그 수준 및 색상 코드를 중앙에서 관리합니다.

이러한 라이브러리를 사용하면 프로젝트의 모든 스크립트가 형식 지정 코드를 중복하지 않고 일관된 타임스탬프 및 색상 코드가 포함된 메시지를 출력합니다.

#!/usr/bin/env bash
# lib/log.sh — Coloured, levelled logging library

[[ -n "${_LIB_LOG_LOADED:-}" ]] && return 0
_LIB_LOG_LOADED=1

# Colour codes (disabled when not writing to a terminal)
_LOG_RED=''; _LOG_YEL=''; _LOG_GRN=''; _LOG_RST=''
if [[ -t 2 ]]; then
    _LOG_RED='\033[0;31m'
    _LOG_YEL='\033[0;33m'
    _LOG_GRN='\033[0;32m'
    _LOG_RST='\033[0m'
fi

_log::_print() {
    local level="$1" colour="$2"; shift 2
    printf "%b[%s]%b %s  %s\n" \
        "$colour" "$level" "$_LOG_RST" \
        "$(date '+%H:%M:%S')" "$*" >&2
}

log::info()  { _log::_print 'INFO ' "$_LOG_GRN" "$@"; }
log::warn()  { _log::_print 'WARN ' "$_LOG_YEL" "$@"; }
log::error() { _log::_print 'ERROR' "$_LOG_RED" "$@"; return 1; }
log::fatal() { _log::_print 'FATAL' "$_LOG_RED" "$@"; exit 1; }

파일 시스템 도우미 라이브러리

파일과 디렉터리를 조작하는 스크립트에서는 동일한 방어적 검사를 반복하는 경우가 많습니다. 이 디렉터리가 존재하는가? 이 경로에 쓸 수 있는가? 중요한 항목을 삭제하려는 것은 아닌가?

이러한 검사를 lib/fs.sh에 중앙화하면 이를 사용하는 모든 스크립트가 더 안전하고 읽기 쉬워집니다. 각 함수가 실패할 때 exit 대신 return 1을 사용하여 호출자가 오류를 적절히 처리할 수 있도록 하는 점에 주목하십시오.

#!/usr/bin/env bash
# lib/fs.sh — Filesystem helper library

[[ -n "${_LIB_FS_LOADED:-}" ]] && return 0
_LIB_FS_LOADED=1

# Ensure a directory exists; create it if not
fs::require_dir() {
    local dir="$1"
    if [[ ! -d "$dir" ]]; then
        mkdir -p "$dir" || { echo "[fs] Cannot create directory: $dir" >&2; return 1; }
    fi
}

# Remove a file only if it exists (no error on missing)
fs::safe_rm() {
    local target="$1"
    [[ -e "$target" ]] && rm -rf -- "$target"
    return 0
}

# Assert that a file exists and is readable
fs::require_file() {
    local file="$1"
    [[ -f "$file" && -r "$file" ]] || {
        echo "[fs] Required file missing or unreadable: $file" >&2
        return 1
    }
}

상수로 라이브러리 버전 관리하기

라이브러리를 여러 프로젝트에서 공유하거나 팀에 배포할 때는 실행 시 어떤 버전의 라이브러리가 로드되었는지 아는 것이 중요해집니다. 간단한 규칙은 각 라이브러리에서 버전 상수를 내보내는 것입니다.

그러면 호출자는 시작할 때 최소 버전을 확인하여 나중에 원인을 알 수 없는 실패를 디버깅하는 대신 불일치를 일찍 발견할 수 있습니다. 보호 변수는 버전 문자열의 역할도 하므로 하나의 변수로 두 가지 책임을 결합합니다.

#!/usr/bin/env bash
# lib/str.sh — versioned example

# Guard doubles as the version identifier
[[ -n "${_LIB_STR_LOADED:-}" ]] && return 0
readonly _LIB_STR_LOADED='1.3.0'

# Caller can validate the version
str::version() { echo "$_LIB_STR_LOADED"; }

# ---- Utility functions ----
str::trim() {
    local s="$1"
    s="${s#"${s%%[![:space:]]*}"}"
    s="${s%"${s##*[![:space:]]}"}"  
    echo "$s"
}

str::repeat() {
    local str="$1" count="$2" result=''
    for (( i=0; i<count; i++ )); do result+="$str"; done
    echo "$result"
}

완결형 데모: 여러 라이브러리 사용하기

이 장면에서는 두 라이브러리를 소스로 불러오고 각 라이브러리의 함수를 사용하는 실제적인 스크립트를 보여 줍니다. 주 스크립트가 깔끔하게 유지되고, 의도만 표현하며 모든 구현 세부 사항은 라이브러리에 포함된다는 점에 주목하십시오.

이 스크립트는 임시 파일에 작성하는 여기 문서를 사용하여 라이브러리를 내부에 정의하므로 독립 실행형 파일로 실행할 수 있습니다. 실제 프로젝트에서는 각 라이브러리가 lib/ 아래의 자체 파일에 들어갑니다.

#!/usr/bin/env bash
# Standalone demo: inline libs written to /tmp, then sourced
set -euo pipefail

# --- Create a temporary lib/log.sh ---
TMPDIR_LIBS="$(mktemp -d)"
trap 'rm -rf "$TMPDIR_LIBS"' EXIT

cat > "${TMPDIR_LIBS}/log.sh" <<'LIBEOF'
[[ -n "${_LIB_LOG_LOADED:-}" ]] && return 0
_LIB_LOG_LOADED=1
log::info()  { echo "[INFO]  $*"; }
log::error() { echo "[ERROR] $*" >&2; return 1; }
LIBEOF

cat > "${TMPDIR_LIBS}/str.sh" <<'LIBEOF'
[[ -n "${_LIB_STR_LOADED:-}" ]] && return 0
_LIB_STR_LOADED=1
str::upper() { echo "${1^^}"; }
str::trim()  { local s="$1"; s="${s#"${s%%[![:space:]]*}"}";
               s="${s%"${s##*[![:space:]]}"}"  ; echo "$s"; }
LIBEOF

# --- Source both libraries ---
source "${TMPDIR_LIBS}/log.sh"
source "${TMPDIR_LIBS}/str.sh"

# --- Main logic ---
log::info "Libraries loaded successfully."
raw_input="   hello from bash libraries   "
trimmed="$(str::trim "$raw_input")"
log::info "Trimmed: '${trimmed}'"
log::info "Uppercased: '$(str::upper "$trimmed")'"

모범 사례와 흔한 실수

팀에서 사용할 라이브러리를 배포하기 전에 다음 점검 목록을 확인하십시오.

  • 포함 보호문 사용 — 모든 라이브러리에 하나씩 있어야 하며, 보호 변수 이름을 맨 위에 문서화하십시오.
  • 최상위 부수 효과 금지 — 함수 본문 밖에서 cd, echo를 실행하거나 전역 상태를 변경하지 마십시오.
  • 함수 내부의 모든 변수에 local 사용 — local이 없으면 모든 대입이 호출자의 범위로 새어 나갑니다.
  • 반환하고 종료하지 않기 — 소스로 불러온 파일 안의 exit는 호출 중인 셸 전체를 종료합니다.
  • 입력 검증 — 필수 인수를 확인하고, 누락된 경우 의미 있는 오류 코드를 반환하십시오.
  • 주석으로 문서화 — 각 함수의 역할, 매개변수 및 반환값을 설명하십시오.
  • 라이브러리 파일 내부에서 set -e 사용 자제 — 호출자마다 자체 오류 처리 전략이 있을 수 있으므로 결정은 호출자에게 맡기십시오.

지식 확인: 포함 보호문

main.sh가 lib/bootstrap.sh와 lib/log.sh를 모두 소스로 불러오고, lib/bootstrap.sh도 내부에서 lib/log.sh를 소스로 불러오는 프로젝트를 생각해 보십시오. lib/log.sh의 포함 보호문이 주로 수행하는 역할은 무엇입니까?

강의 요약: 올바른 Bash 라이브러리

재사용 가능한 Bash 라이브러리를 만들고 사용하는 데 필요한 모든 핵심 기법을 살펴보았습니다. 다음 내용을 기억하십시오.

  • source 또는 .로 소스 불러오기 — 파일을 현재 셸에 로드하여 해당 함수들을 즉시 사용할 수 있게 합니다.
  • $BASH_SOURCE 사용 — 호출하는 스크립트를 기준으로 라이브러리 경로를 확인하여 프로젝트의 이식성을 유지합니다.
  • 포함 보호문 ([[ -n "${_GUARD:-}" ]] && return 0) — 여러 파일이 같은 라이브러리를 소스로 불러올 때 중복 정의를 방지합니다.
  • 이름 공간 접두사 (log::, str::, fs::) — 라이브러리 간 함수 이름 충돌을 없앱니다.
  • lib/ 디렉터리와 역할이 명확한 단일 책임 파일을 사용하면 대규모 프로젝트를 유지 관리하기 쉽습니다.
  • 부트스트랩 로더 (lib/bootstrap.sh) — 모든 스크립트가 전체 생태계를 로드할 수 있도록 단일 소스 호출을 제공합니다.
  • 라이브러리 파일에서 exit 또는 최상위 부수 효과를 절대 사용하지 않기 — 해당 위치에는 함수 정의와 상수만 있어야 합니다.

이러한 패턴을 일관되게 적용하면 셸 스크립트도 더 높은 수준의 언어로 작성된 코드만큼 모듈화되고 유지 관리하기 쉬워집니다.

자주 묻는 질문

“재사용 가능한 Bash 라이브러리 구축과 불러오기” 강의는 무료인가요?

네 — “재사용 가능한 Bash 라이브러리 구축과 불러오기” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 DevOps Bootcamp 강의 전체를 잠금 해제할 수 있습니다. DevOps Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.

“재사용 가능한 Bash 라이브러리 구축과 불러오기”에서 뭘 배우나요?

공유 도우미를 포함 방지 기능과 네임스페이스가 지정된 함수 접두사를 갖춘 불러올 수 있는 .sh 라이브러리 파일로 구성하는 방법을 배웁니다. 브라우저에서 직접 실행하는 실습 코드로 DevOps Bootcamp을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

DevOps Bootcamp을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 DevOps Bootcamp은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.

“재사용 가능한 Bash 라이브러리 구축과 불러오기” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 DevOps Bootcamp 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 DevOps Bootcamp 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 로컬 범위와 반환 코드로 함수 설계하기
  2. 재사용 가능한 Bash 라이브러리 구축과 불러오기
  3. getopts로 플래그와 인수 파싱하기
  4. 함수 간 배열과 연관 맵 전달하기
← DevOps Bootcamp(으)로 돌아가기