Skip to content

Instantly share code, notes, and snippets.

@m-fire
Last active May 3, 2025 05:55
Show Gist options
  • Save m-fire/380efe79a1e9dbb637abd15e81d1fa0f to your computer and use it in GitHub Desktop.
Save m-fire/380efe79a1e9dbb637abd15e81d1fa0f to your computer and use it in GitHub Desktop.
특정 깃허브 사용자의 저장소에 릴리즈 된 Assets 다운로드 스크립트(종류별 확장자지정 가능)
#!/bin/bash
# ==============================================================================
# GitHub 최신 릴리즈 에셋 병렬 다운로드 스크립트
# ==============================================================================
#
# 사용법: ./download-github-assets.sh <소유자> <저장소> <다운로드_경로> [옵션]
# 예시: ./download-github-assets.sh octocat Spoon-Knife ./downloads -e zip,tar.gz
#
# ==============================================================================
# --- 필수 명령어 확인 ---
command -v curl >/dev/null 2>&1 || { echo >&2 "오류: 'curl' 명령어를 찾을 수 없습니다. 설치 후 다시 시도해주세요."; exit 1; }
command -v jq >/dev/null 2>&1 || { echo >&2 "오류: 'jq' 명령어를 찾을 수 없습니다. 설치 후 다시 시도해주세요."; exit 1; }
# --- 도움말 함수 ---
usage() {
echo -e ""
echo -e "--------------------------------------------------"
echo -e "🌟 GitHub Asset 다운로드 🌟"
echo -e "--------------------------------------------------"
echo -e "사용법: $(basename "$0") <repo-owner> <repo-name> <download-path> [options]"
echo -e "예시) $ $(basename "$0") my-name repo-name ./download -e zip,tar.gz"
echo -e ""
echo -e "필수 인자:"
echo -e " <repo-owner> : GitHub 저장소 소유자 이름"
echo -e " <repo-name> : GitHub 저장소 이름"
echo -e " <download-path> : 파일들이 저장될 로컬 디렉토리"
echo -e ""
echo -e "옵션:"
echo -e " -e, --ext : 다운로드 할 확장자 목록 (쉼표로 구분, 기본값: \"zip\") 예: zip,txt,md"
echo -e " -h, --help : 도움말 출력"
exit 1
}
# --- 인자 파싱 ---
# 도움말 옵션 확인 (개별 인자 확인 방식)
for arg in "$@"; do
if [[ "$arg" == "-h" ]] || [[ "$arg" == "--help" ]]; then
usage
fi
done
# 필수 인자 개수 확인 (최소 3개)
if [ "$#" -lt 3 ]; then
echo "❌ 오류: 필수 인자가 부족합니다. (현재 $# 개)"
usage
fi
# 필수 인자 할당
REPO_OWNER="$1"
REPO_NAME="$2"
DOWNLOAD_TO="$3"
shift 3 # 처리된 필수 인자 제거
# 옵션 기본값 설정
EXTENSIONS_STRING="zip" # 다운로드할 확장자 문자열 (쉼표 구분)
# 옵션 파싱
while [ "$#" -gt 0 ]; do
case "$1" in
-e|--ext)
if [ -z "$2" ]; then
echo "❌ 오류: -e 또는 --ext 옵션에는 확장자 목록이 필요합니다."
usage
fi
EXTENSIONS_STRING="$2"
shift 2 # 처리된 옵션과 값 (2개)을 인자 목록에서 제거
;;
*) # 그 외의 알 수 없는 옵션 처리
shift # 알 수 없는 인자는 건너뛰기
;;
esac
done
# 확장자 문자열을 배열로 변환 (쉼표 기준)
IFS=',' read -ra DOWNLOAD_EXTS <<< "$EXTENSIONS_STRING"
# --- 설정 정보 출력 ---
echo -e ""
echo -e "🚀 다운로드 설정:"
echo -e " From\t: ${REPO_OWNER}/${REPO_NAME}"
echo -e " To\t: ${DOWNLOAD_TO}"
echo -e " 확장자\t: [${EXTENSIONS_STRING}]"
echo -e ""
# --- GitHub API 요청 ---
API_URL="https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest"
echo -e "🔍 GitHub API 요청: ${API_URL}"
# curl을 사용하여 API 요청 및 응답 저장
response=$(curl -fsSL "$API_URL")
curl_exit_code=$?
# curl 실행 오류 확인
if [ $curl_exit_code -ne 0 ]; then
echo "❌ 오류: GitHub API 요청 실패 (curl 종료 코드: $curl_exit_code)."
echo " URL: $API_URL"
echo " 저장소 이름과 소유자가 올바른지, 네트워크 연결 상태를 확인하세요."
exit 1
fi
# 응답이 비어있는 경우
if [ -z "$response" ]; then
echo "❌ 오류: GitHub API로부터 비어있는 응답을 받았습니다."
exit 1
fi
# jq를 사용하여 API 응답에서 오류 메시지 확인
message=$(echo "$response" | jq -r '.message // "null"')
jq_exit_code=$?
if [ $jq_exit_code -ne 0 ]; then
echo "❌ 오류: jq 실행 실패 (종료 코드: $jq_exit_code). API 응답 처리 중 문제 발생."
echo " API 응답 내용:"
echo "$response"
exit 1
fi
# API 자체에서 오류 메시지를 반환했는지 확인
if [[ "$message" != "null" ]]; then
echo "❌ GitHub API 오류: $message"
echo " (저장소가 존재하지 않거나, 릴리즈가 없을 수 있습니다.)"
exit 1
fi
# --- 릴리즈 정보 및 다운로드 준비 ---
echo "--- 릴리즈 정보 및 다운로드 준비 ---"
# 최신 릴리즈 태그 추출 및 출력
latest_tag=$(echo "$response" | jq -r '.tag_name // ""')
if [[ -z "$latest_tag" ]]; then
echo "⚠️ 경고: 최신 릴리즈 태그 정보를 찾을 수 없습니다."
else
echo -e "🌈 최신 릴리즈 태그: $latest_tag"
fi
# 다운로드 대상 디렉토리 준비
echo -e "\n🧹 다운로드 디렉토리 준비: $DOWNLOAD_TO"
if [ -d "$DOWNLOAD_TO" ]; then
echo " 기존 디렉토리를 삭제합니다."
rm -rf "$DOWNLOAD_TO"
if [ $? -ne 0 ]; then
echo "❌ 오류: 기존 디렉토리 삭제 실패: $DOWNLOAD_TO"
exit 1
fi
fi
mkdir -p "$DOWNLOAD_TO"
if [ $? -ne 0 ]; then
echo "❌ 오류: 다운로드 디렉토리 생성 실패: $DOWNLOAD_TO"
exit 1
fi
echo "다운로드 디렉토리 준비 완료."
# --- 에셋 병렬 다운로드 ---
echo -e "\n⬇️ 에셋 파일 병렬 다운로드 시작:"
asset_count=0
download_pids=() # 백그라운드 다운로드 프로세스 ID(PID)를 저장할 배열
# jq를 사용하여 assets 배열 처리
while IFS= read -r asset_json; do
if [ -z "$asset_json" ]; then
continue
fi
asset_count=$((asset_count + 1))
asset_name=$(echo "$asset_json" | jq -r '.name // ""')
download_url=$(echo "$asset_json" | jq -r '.browser_download_url // ""')
if [[ -z "$asset_name" ]] || [[ -z "$download_url" ]]; then
echo "⏩ 건너뜀: 에셋 정보 파싱 오류 (JSON: $asset_json)"
continue
fi
should_download=false
for ext in "${DOWNLOAD_EXTS[@]}"; do
trimmed_ext=$(echo "$ext" | xargs)
if [[ -n "$trimmed_ext" && "$asset_name" == *."$trimmed_ext" ]]; then
should_download=true
break
fi
done
if [ "$should_download" = true ]; then
echo -e "📥 백그라운드 다운로드 시작: $asset_name"
# curl 명령어를 백그라운드에서 실행하고 '&' PID를 배열에 저장
( # 서브쉘에서 실행하여 개별 오류 처리 용이
curl -sSL -o "$DOWNLOAD_TO/$asset_name" "$download_url"
# 개별 다운로드 성공/실패 메시지 (선택 사항)
# if [ $? -eq 0 ]; then
# echo " ✅ 완료: $asset_name"
# else
# echo " ❌ 실패: $asset_name (오류 코드: $?)"
# # 실패 시 임시 파일 삭제 등 추가 처리 가능
# # rm -f "$DOWNLOAD_TO/$asset_name"
# fi
) & # '&'를 붙여 백그라운드 실행
download_pids+=($!) # 마지막 백그라운드 프로세스의 PID 저장
else
echo -e "⏩ 건너뜀 (확장자 불일치): $asset_name"
fi
done < <(echo "$response" | jq -c '.assets[]?')
# --- 모든 백그라운드 다운로드 완료 대기 ---
echo -e "\n⏳ 모든 다운로드가 완료될 때까지 기다립니다..."
wait_count=0
total_pids=${#download_pids[@]}
failed_pids=()
# 저장된 모든 PID에 대해 wait 명령어 실행
for pid in "${download_pids[@]}"; do
wait "$pid" # 해당 PID의 프로세스가 종료될 때까지 기다림
exit_code=$? # 프로세스의 종료 코드 확인
if [ $exit_code -ne 0 ]; then
echo "⚠️ 경고: PID $pid (다운로드 프로세스)가 오류 코드 $exit_code 로 종료되었습니다."
failed_pids+=($pid)
fi
wait_count=$((wait_count + 1))
# 진행률 표시 (선택 사항)
# echo -ne " 진행률: $wait_count / $total_pids \r"
done
echo -e "\n모든 백그라운드 작업 확인 완료."
# --- 완료 메시지 ---
echo -e "\n--------------------------------------------------"
download_count=$((total_pids - ${#failed_pids[@]})) # 성공한 다운로드 수 계산
if [ $asset_count -eq 0 ]; then
echo "ℹ️ 정보: 최신 릴리즈에 다운로드할 에셋이 없습니다."
elif [ $total_pids -eq 0 ]; then # 다운로드를 시도한 파일이 없는 경우
echo "⚠️ 경고: 지정된 확장자와 일치하는 에셋이 없어 다운로드된 파일이 없습니다."
elif [ ${#failed_pids[@]} -gt 0 ]; then # 실패한 다운로드가 있는 경우
echo "⚠️ 경고: 총 ${total_pids}개 중 ${download_count}개의 파일 다운로드를 완료했습니다. (${#failed_pids[@]}개 실패)"
echo " 실패한 프로세스 PID: ${failed_pids[*]}"
else # 모든 다운로드가 성공한 경우
echo "✅ 다운로드 완료! 총 ${download_count}개의 파일을 받았습니다."
fi
# tree 명령어가 존재하고, 다운로드된 파일이 있을 경우 tree 구조 출력
if command -v tree >/dev/null 2>&1 && [ $download_count -gt 0 ]; then
tree "$DOWNLOAD_TO"
elif [ $download_count -gt 0 ]; then
echo -e "\nℹ️ 'tree' 명령어를 찾을 수 없어 파일 구조를 표시할 수 없습니다."
fi
echo -e "--------------------------------------------------"
# 실패한 다운로드가 있으면 0이 아닌 종료 코드 반환 (선택 사항)
if [ ${#failed_pids[@]} -gt 0 ]; then
exit 1
else
exit 0
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment