배열 순회와 조작
배열을 만들었으면 그 안의 값을 하나씩 처리해야 할 때가 많습니다. 전부 출력하거나, 조건에 맞는 것만 고르거나, 정렬하거나. 이번 절에서는 배열을 다루는 실용적인 기법들을 정리합니다.
for loop로 배열 순회
가장 기본적인 순회는 for ... in "${arr[@]}" 패턴입니다.
fruits=("apple" "banana" "cherry mango" "date")# 요소 순회 — "를 반드시 써야 공백 포함 요소 안전for fruit in "${fruits[@]}"; do echo "과일: $fruit"done
출력 결과입니다.
과일: apple
과일: banana
과일: cherry mango
과일: date
"${fruits[@]}"에서 큰따옴표가 중요합니다. 따옴표 없이 ${fruits[@]}를 쓰면 "cherry mango"가 두 개의 인수로 쪼개집니다.
인덱스와 함께 순회하려면 C 스타일 for문을 씁니다.
fruits=("apple" "banana" "cherry")for (( i = 0; i < ${#fruits[@]}; i++ )); do echo "[$i] ${fruits[$i]}"done
출력 결과입니다.
[0] apple
[1] banana
[2] cherry
연관 배열은 키 목록(${!assoc[@]})으로 순회합니다.
declare -A scoresscores["철수"]=85scores["영희"]=92scores["민수"]=78for name in "${!scores[@]}"; do echo "$name: ${scores[$name]}점"done
배열 필터링
조건에 맞는 요소만 골라 새 배열을 만드는 패턴입니다.
numbers=(3 17 42 8 55 23 100 6 71 14)# 50 이상인 수만 필터링large=()for num in "${numbers[@]}"; do if (( num >= 50 )); then large+=("$num") fidoneecho "원본: ${numbers[@]}"echo "50 이상: ${large[@]}"
출력 결과입니다.
원본: 3 17 42 8 55 23 100 6 71 14
50 이상: 55 100 71
문자열 배열에서 패턴으로 필터링하는 예입니다.
files=("report.txt" "image.png" "data.csv" "backup.txt" "graph.png")# .txt 파일만 골라내기txt_files=()for f in "${files[@]}"; do if [[ "$f" == *.txt ]]; then txt_files+=("$f") fidoneecho "텍스트 파일: ${txt_files[@]}"
출력 결과입니다.
텍스트 파일: report.txt backup.txt
배열 정렬
Bash 배열 자체에는 정렬 기능이 없습니다. sort 명령어로 정렬한 결과를 readarray(또는 mapfile)로 배열에 담는 방법을 씁니다.
words=("banana" "apple" "cherry" "date" "elderberry")# 문자열 정렬readarray -t sorted < <(printf '%s\n' "${words[@]}" | sort)echo "정렬 전: ${words[@]}"echo "정렬 후: ${sorted[@]}"
출력 결과입니다.
정렬 전: banana apple cherry date elderberry
정렬 후: apple banana cherry date elderberry
숫자 정렬은 sort -n을 씁니다.
nums=(42 7 19 100 3 55)readarray -t sorted_nums < <(printf '%s\n' "${nums[@]}" | sort -n)echo "숫자 정렬: ${sorted_nums[@]}"# 역순readarray -t sorted_desc < <(printf '%s\n' "${nums[@]}" | sort -rn)echo "역순 정렬: ${sorted_desc[@]}"
출력 결과입니다.
숫자 정렬: 3 7 19 42 55 100
역순 정렬: 100 55 42 19 7 3
readarray -t는 줄 단위로 읽어서 배열에 담습니다. -t는 줄 끝의 개행문자를 제거합니다. < <(...)는 프로세스 치환으로, 명령어 출력을 마치 파일처럼 읽어옵니다.
배열을 함수 인수로 전달하는 방법
Bash에서 배열을 함수에 그대로 전달하는 방법은 두 가지입니다.
첫 번째는 배열 요소를 풀어서 전달하는 방법입니다. 함수 안에서는 $@으로 받습니다.
print_all() { echo "인수 개수: $#" for item in "$@"; do echo " - $item" done}fruits=("apple" "banana" "cherry")print_all "${fruits[@]}" # 배열 요소를 풀어서 전달
두 번째는 배열 이름을 전달하고 간접 참조를 쓰는 방법입니다. Bash 4.3+에서 declare -n(nameref)을 사용합니다.
sum_array() { local -n arr=$1 # 배열 이름으로 참조 local total=0 for val in "${arr[@]}"; do (( total += val )) done echo "$total"}nums=(10 20 30 40)result=$(sum_array nums) # 배열 이름을 문자열로 전달echo "합계: $result" # 합계: 100
local -n arr=$1은 arr이 nums 배열을 가리키는 참조(nameref)가 됩니다. 함수 안에서 arr을 쓰면 실제로는 nums를 조작합니다. 큰 배열을 복사 없이 함수에 전달할 때 유용합니다.
실습: 파일 목록 배열로 관리하기
#!/bin/bash# 새 파일: file_manager.sh# 현재 디렉토리의 파일 목록을 배열로 읽기readarray -t all_files < <(find . -maxdepth 1 -type f | sort)echo "=== 전체 파일 목록 ==="for (( i = 0; i < ${#all_files[@]}; i++ )); do printf " [%2d] %s\n" "$i" "${all_files[$i]}"doneecho "총 ${#all_files[@]}개"# 확장자별 필터링 함수filter_by_ext() { local -n result=$1 # 결과 배열 (nameref) local ext="$2" result=() for f in "${all_files[@]}"; do if [[ "$f" == *."$ext" ]]; then result+=("$f") fi done}echo ""echo "=== 확장자별 분류 ==="declare -a sh_filesdeclare -a md_filesfilter_by_ext sh_files "sh"filter_by_ext md_files "md"echo ".sh 파일 (${#sh_files[@]}개):"for f in "${sh_files[@]}"; do echo " $f"doneecho ".md 파일 (${#md_files[@]}개):"for f in "${md_files[@]}"; do echo " $f"done# 크기 기준 정렬 (가장 큰 파일 순)echo ""echo "=== 크기 기준 상위 5개 ==="readarray -t large_files < <(find . -maxdepth 1 -type f -printf "%s\t%f\n" | sort -rn | head -5)for entry in "${large_files[@]}"; do size=$(echo "$entry" | cut -f1) name=$(echo "$entry" | cut -f2) printf " %8d bytes %s\n" "$size" "$name"done
readarray -t all_files < <(find ...) 패턴은 파일 목록처럼 외부 명령어 결과를 배열로 받을 때 자주 쓰입니다. filter_by_ext 함수는 nameref로 결과 배열을 직접 채워줍니다. 반환값으로 배열을 돌려주기 어려운 Bash에서 nameref가 좋은 대안이 됩니다.