728x90
가끔씩 필요해서 찾게 되는 것이라 정리해 봅니다.
1. sftp-config.sh (설정 파일)
용량 비교 및 상세 체크를 위해 SSH 접속 정보를 그대로 사용합니다.
더보기
# sftp-config.sh
# SFTP 서버 접속 정보 및 경로 설정
# [필수] SFTP 서버 정보
SFTP_HOST="sftp.example.com"
SFTP_PORT="22"
SFTP_USER="root"
# [선택] RSA 개인키 경로 (기본: /root/.ssh/id_rsa)
IDENTITY_FILE="/root/.ssh/id_rsa"
# 재시도 설정
MAX_RETRIES=2
BACKOFF_BASE=2
# 로그 및 경로 설정
LOG_BASE_DIR="/root/sftp_logs" # 상세 실행 로그
RESULT_LOG_DIR="/root/sftp_results" # 운영용 요약 로그
DB_READY_LOG_DIR="/root/sftp_db_logs" # DB 적재용 정제 로그 (JSONL)
TARGET_LIST_DIR="/root/sftp_targets"
# 동일 용량 파일 스킵 여부 (YES: 스킵 및 로그 남김, NO: 덮어쓰기)
SKIP_SAME_SIZE="YES"
2. sftp_download.sh (고도화된 전체 소스)
이 스크립트는 각 파일의 상태를 4가지 상태(SUCCESS, NOT_FOUND, SKIPPED_SAME, FAILED)로 구분하여 로그를 남깁니다.
더보기
#!/usr/bin/env bash
# sftp_download.sh - DB 연동 최적화 및 상세 로깅 버전
set -uo pipefail
BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="${BASE_DIR}/sftp-config.sh"
MODE=""
REMOTE_BASE=""
PATTERN=""
TARGET_LIST=""
LOCAL_DIR=""
usage() {
cat <<EOF
Usage: $(basename "$0") -M <mode> -r <remote_base> -d <local_abs_path> [options]
Modes: tree | list
EOF
}
# 1. 텍스트 로그 (운영자 확인용)
log() {
local level="$1"; shift
local msg="$*"
echo "$(date '+%Y-%m-%d %H:%M:%S') [${level}] ${msg}" | tee -a "${LOG_FILE}"
}
# 2. 정제 로그 (DB 적재 프로그램용 - JSONL)
record_db_log() {
local status="$1"
local file_path="$2"
local size="${3:-0}"
local msg="${4:-""}"
local ts
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ") # ISO 8601 표준
# DB 프로그램에서 파싱하기 좋게 한 줄의 JSON으로 기록
printf '{"ts":"%s", "status":"%s", "file":"%s", "size":%d, "msg":"%s"}\n' \
"$ts" "$status" "$file_path" "$size" "$msg" >> "${DB_LOG}"
}
while getopts "M:r:p:f:d:c:h" opt; do
case "${opt}" in
M) MODE="${OPTARG}" ;;
r) REMOTE_BASE="${OPTARG}" ;;
p) PATTERN="${OPTARG}" ;;
f) TARGET_LIST="${OPTARG}" ;;
d) LOCAL_DIR="${OPTARG}" ;;
c) CONFIG_FILE="${OPTARG}" ;;
h) usage; exit 0 ;;
*) usage; exit 1 ;;
esac
done
if [[ ! -f "${CONFIG_FILE}" ]]; then
echo "설정 파일을 찾을 수 없습니다: ${CONFIG_FILE}" >&2
exit 1
fi
source "${CONFIG_FILE}"
# 디렉토리 및 로그 파일 준비
mkdir -p "${LOG_BASE_DIR}" "${RESULT_LOG_DIR}" "${DB_READY_LOG_DIR}" "${LOCAL_DIR}"
LOG_FILE="${LOG_BASE_DIR}/sftp_detail_$(date +%Y%m%d).log"
DB_LOG="${DB_READY_LOG_DIR}/transfer_data_$(date +%Y%m%d).jsonl"
SSH_OPTS="-i ${IDENTITY_FILE} -p ${SFTP_PORT} -o BatchMode=yes -o StrictHostKeyChecking=yes -o ServerAliveInterval=60"
download_file() {
local rel_path="$1"
local remote_p="${REMOTE_BASE}/${rel_path}"
local local_p="${LOCAL_DIR}/${rel_path}"
# 1. 원격 파일 정보 획득
local r_size
r_size=$(ssh ${SSH_OPTS} "${SFTP_USER}@${SFTP_HOST}" "stat -c%s '${remote_p}'" 2>/dev/null || echo "NOT_FOUND")
if [[ "${r_size}" == "NOT_FOUND" ]]; then
log WARN "파일 없음: ${rel_path}"
record_db_log "NOT_FOUND" "${rel_path}" 0 "Remote file not found"
return 1
fi
# 2. 로컬 파일과 용량 비교 (중복 체크)
if [[ -f "${local_p}" ]]; then
local l_size
l_size=$(stat -c%s "${local_p}")
if [[ "${r_size}" == "${l_size}" && "${SKIP_SAME_SIZE}" == "YES" ]]; then
log INFO "스킵(동일 용량): ${rel_path}"
record_db_log "SKIPPED" "${rel_path}" "${r_size}" "Same size skip"
return 0
fi
fi
# 3. 실제 SFTP 전송
mkdir -p "$(dirname "${local_p}")"
if sftp -b - -P "${SFTP_PORT}" -i "${IDENTITY_FILE}" -C -o BatchMode=yes "${SFTP_USER}@${SFTP_HOST}" <<EOF >> "${LOG_FILE}" 2>&1
get "${remote_p}" "${local_p}"
quit
EOF
then
log INFO "성공: ${rel_path}"
record_db_log "SUCCESS" "${rel_path}" "${r_size}" "Transfer complete"
else
log ERROR "실패: ${rel_path}"
record_db_log "FAILED" "${rel_path}" "${r_size}" "SFTP error during transfer"
return 1
fi
}
log INFO "=== 작업 시작 (Mode: ${MODE}) ==="
if [[ "${MODE}" == "list" ]]; then
# 리스트 파일이 지정되지 않았다면 TARGET_LIST_DIR의 첫 번째 파일 사용
[[ -z "${TARGET_LIST}" ]] && TARGET_LIST=$(find "${TARGET_LIST_DIR}" -type f | head -n 1)
if [[ -f "${TARGET_LIST}" ]]; then
while IFS= read -r line || [[ -n "$line" ]]; do
[[ -z "$line" || "$line" =~ ^# ]] && continue
download_file "${line}"
done < "${TARGET_LIST}"
else
log ERROR "리스트 파일을 찾을 수 없습니다."
fi
elif [[ "${MODE}" == "tree" ]]; then
# 원격지 패턴 검색 후 목록 기반 다운로드
log INFO "원격지 목록 조회 중..."
file_list=$(ssh ${SSH_OPTS} "${SFTP_USER}@${SFTP_HOST}" "find '${REMOTE_BASE}' -type f -name '${PATTERN}'" | sed "s|${REMOTE_BASE}/||")
for f in ${file_list}; do
download_file "$f"
done
fi
log INFO "=== 작업 종료 (결과: ${DB_LOG}) ==="
3. 스크립트의 특징 (운영 강점)
상세한 상태 분류:
SUCCESS: 파일이 성공적으로 전송됨.
NOT_FOUND: 원격 서버에 대상 파일이 아예 없음 (경로 오타 등).
SKIPPED: 이미 로컬에 같은 용량의 파일이 있어 전송을 생략함 (네트워크 자원 절약).
FAILED: 파일은 있으나 권한 문제, 네트워크 끊김 등으로 가져오지 못함.
용량 기반 검증: 단순히 파일명만 보는 게 아니라 stat 명령으로 용량을 비교하여 '정상적으로 가져온 상태'를 확신할 수 있습니다.
가독성 높은 요약 로그: RESULT_LOG 파일만 열어보면 어떤 파일이 어떤 이유로 처리되었는지 한눈에 알 수 있어 운영이 편리합니다.
더보기
- DB 적재용 로그 샘플 (transfer_data_YYYYMMDD.jsonl):
-
JSON
{"ts":"2026-03-18T00:50:01Z", "status":"SUCCESS", "file":"daily/data.csv", "size":1048, "msg":"Transfer complete"} {"ts":"2026-03-18T00:50:03Z", "status":"SKIPPED", "file":"backup/db.sql", "size":50020, "msg":"Same size skip"} - 후속 프로그램과의 연계:
- 후속 프로그램(Java/Python 등)이 실행될 때 DB_READY_LOG_DIR 내의 파일을 한 줄씩 읽습니다.
- status가 SUCCESS면 DB의 전송 완료 테이블에 INSERT 합니다.
- status가 FAILED면 관리자 알림 테이블에 넣거나 재시도 큐에 넣습니다.
- 성능 최적화:
- -C 옵션을 통해 다량의 파일 전송 시 데이터를 압축하여 대역폭을 절약합니다.
- ServerAliveInterval을 설정하여 대용량 파일 전송 중 연결 끊김을 방지합니다.
4. 사용 예제
Tree 모드 (패턴)
./sftp_download.sh -M tree -r /data/logs -p "*.csv" -d /root/downloads
동작: /data/logs에서 .csv를 찾아 리스트를 만든 후, 하나씩 용량 체크하며 가져옵니다.
List 모드 (파일명 나열)
./sftp_download.sh -M list -r /data/origin -f targets.txt -d /root/downloads
급하게 정리해봅니다.
728x90