배열과 데이터 구조 ⭐⭐
배열과 데이터 구조 ⭐⭐¶
이전: 02_Parameter_Expansion.md | 다음: 04_Advanced_Control_Flow.md
이 레슨에서는 bash 배열과 구조화된 데이터 작업 방법을 다룹니다. 인덱스 배열과 연관 배열, 일반적인 데이터 구조 패턴, CSV 및 설정 파일 파싱, 쉘 스크립트에서 복잡한 데이터를 관리하는 실용적인 기술을 탐구합니다.
1. 인덱스 배열 연산¶
인덱스 배열(Indexed Arrays)은 0부터 시작하는 숫자 인덱스로 요소를 저장합니다.
배열 선언 및 초기화¶
#!/usr/bin/env bash
# Empty array
declare -a empty_array
# Array with initial values
fruits=("apple" "banana" "cherry")
# Explicit declaration
declare -a numbers=(1 2 3 4 5)
# Sparse array (indices don't need to be continuous)
sparse[0]="first"
sparse[5]="sixth"
sparse[10]="eleventh"
# Multi-line declaration
servers=(
"web1.example.com"
"web2.example.com"
"web3.example.com"
)
# From command output
files=(*.txt) # All .txt files in current directory
lines=($(cat file.txt)) # WARNING: splits on whitespace
# Safe way to read lines into array
mapfile -t lines < file.txt
# or
readarray -t lines < file.txt
배열 요소 접근¶
#!/usr/bin/env bash
fruits=("apple" "banana" "cherry" "date")
# Access single element
echo "${fruits[0]}" # apple
echo "${fruits[2]}" # cherry
# Access last element
echo "${fruits[-1]}" # date (bash 4.3+)
echo "${fruits[@]: -1}" # date (older bash)
# Access all elements
echo "${fruits[@]}" # apple banana cherry date
echo "${fruits[*]}" # apple banana cherry date
# Difference between @ and *
for fruit in "${fruits[@]}"; do
echo "Fruit: $fruit"
done
# Iterates 4 times, one per element
for fruit in "${fruits[*]}"; do
echo "Fruit: $fruit"
done
# Iterates 1 time, all elements as single string
# Array length
echo "${#fruits[@]}" # 4 (number of elements)
echo "${#fruits[*]}" # 4 (same)
# Length of specific element
echo "${#fruits[1]}" # 6 (length of "banana")
배열 수정¶
#!/usr/bin/env bash
# Append elements
fruits=("apple" "banana")
fruits+=("cherry") # Add one
fruits+=("date" "elderberry") # Add multiple
echo "${fruits[@]}"
# apple banana cherry date elderberry
# Modify element
fruits[1]="blueberry"
echo "${fruits[@]}"
# apple blueberry cherry date elderberry
# Delete element (creates sparse array)
unset 'fruits[2]'
echo "${fruits[@]}" # apple blueberry date elderberry
echo "${#fruits[@]}" # 4 (still 4 elements)
echo "${!fruits[@]}" # 0 1 3 4 (indices)
# Delete entire array
unset fruits
# Prepend elements (create new array)
nums=(1 2 3)
nums=(0 "${nums[@]}")
echo "${nums[@]}" # 0 1 2 3
# Insert in middle (requires recreation)
arr=(a b c d)
arr=("${arr[@]:0:2}" "X" "${arr[@]:2}")
echo "${arr[@]}" # a b X c d
배열 슬라이싱¶
#!/usr/bin/env bash
# ${array[@]:start:length}
numbers=(0 1 2 3 4 5 6 7 8 9)
# Extract from index 2, length 3
echo "${numbers[@]:2:3}" # 2 3 4
# Extract from index 5 to end
echo "${numbers[@]:5}" # 5 6 7 8 9
# Extract last 3 elements
echo "${numbers[@]: -3}" # 7 8 9
# Copy array
copy=("${numbers[@]}")
echo "${copy[@]}"
# Copy slice
subset=("${numbers[@]:2:5}")
echo "${subset[@]}" # 2 3 4 5 6
반복¶
#!/usr/bin/env bash
servers=("web1" "web2" "db1" "cache1")
# Iterate over values
for server in "${servers[@]}"; do
echo "Processing: $server"
done
# Iterate over indices
for i in "${!servers[@]}"; do
echo "Server $i: ${servers[$i]}"
done
# Iterate with index using C-style for
for ((i=0; i<${#servers[@]}; i++)); do
echo "[$i] = ${servers[$i]}"
done
# Iterate in reverse
for ((i=${#servers[@]}-1; i>=0; i--)); do
echo "${servers[$i]}"
done
배열 복사 및 병합¶
#!/usr/bin/env bash
# Copy array
original=(1 2 3)
copy=("${original[@]}")
copy[0]=99
echo "Original: ${original[@]}" # 1 2 3
echo "Copy: ${copy[@]}" # 99 2 3
# Merge arrays
arr1=(a b c)
arr2=(d e f)
merged=("${arr1[@]}" "${arr2[@]}")
echo "${merged[@]}" # a b c d e f
# Merge multiple arrays
arr3=(g h i)
all=("${arr1[@]}" "${arr2[@]}" "${arr3[@]}")
echo "${all[@]}" # a b c d e f g h i
# Append array to array
arr1+=(x y z)
echo "${arr1[@]}" # a b c x y z
2. 연관 배열¶
연관 배열(Associative Arrays, 해시맵, 딕셔너리)은 숫자 인덱스 대신 문자열을 키로 사용합니다. Bash 4.0+ 이상에서 사용 가능합니다.
선언 및 기본 연산¶
#!/usr/bin/env bash
# Must declare as associative array
declare -A config
# Assign values
config[host]="localhost"
config[port]=8080
config[database]="myapp"
# Alternative: initialize at declaration
declare -A user=(
[name]="John Doe"
[email]="john@example.com"
[role]="admin"
)
# Access values
echo "${config[host]}" # localhost
echo "${user[name]}" # John Doe
# Check if key exists
if [[ -v config[host] ]]; then
echo "Host is configured"
fi
# Alternative key existence check
if [[ "${config[host]+exists}" == "exists" ]]; then
echo "Host key exists"
fi
# Get all keys
echo "${!config[@]}" # host port database
# Get all values
echo "${config[@]}" # localhost 8080 myapp
# Number of entries
echo "${#config[@]}" # 3
반복¶
#!/usr/bin/env bash
declare -A settings=(
[theme]="dark"
[language]="en"
[notifications]="enabled"
[auto_save]="true"
)
# Iterate over keys
for key in "${!settings[@]}"; do
echo "$key = ${settings[$key]}"
done
# Iterate over values
for value in "${settings[@]}"; do
echo "Value: $value"
done
# Sort keys before iteration
for key in $(echo "${!settings[@]}" | tr ' ' '\n' | sort); do
echo "$key = ${settings[$key]}"
done
비교: 인덱스 배열 vs 연관 배열¶
| 기능 | 인덱스 배열 | 연관 배열 |
|---|---|---|
| 선언 | arr=() 또는 declare -a arr |
declare -A arr |
| 키 | 정수 (0, 1, 2, ...) | 문자열 |
| 순서 | 보존됨 | 정렬되지 않음 |
| 사용 가능 버전 | 모든 bash 버전 | Bash 4.0+ |
| 사용 사례 | 순차 데이터 | 키-값 쌍 |
| 반복 | 순서 보장됨 | 순서 정의되지 않음 |
| 접근 | ${arr[0]}` | `${arr[key]} |
|
| 모든 키 | ${!arr[@]}` | `${!arr[@]} |
실용 예제¶
#!/usr/bin/env bash
# HTTP status codes
declare -A http_status=(
[200]="OK"
[201]="Created"
[301]="Moved Permanently"
[400]="Bad Request"
[401]="Unauthorized"
[403]="Forbidden"
[404]="Not Found"
[500]="Internal Server Error"
)
lookup_status() {
local code="$1"
if [[ -v http_status[$code] ]]; then
echo "$code ${http_status[$code]}"
else
echo "$code Unknown"
fi
}
lookup_status 200 # 200 OK
lookup_status 404 # 404 Not Found
lookup_status 999 # 999 Unknown
# Environment-specific configuration
declare -A db_config_dev=(
[host]="localhost"
[port]=5432
[name]="dev_db"
[user]="dev_user"
)
declare -A db_config_prod=(
[host]="db.example.com"
[port]=5432
[name]="prod_db"
[user]="app_user"
)
get_db_config() {
local env="$1"
local -n config_ref="db_config_$env"
echo "Database configuration for $env:"
for key in "${!config_ref[@]}"; do
echo " $key: ${config_ref[$key]}"
done
}
get_db_config "dev"
get_db_config "prod"
중첩 데이터 시뮬레이션¶
#!/usr/bin/env bash
# Simulate nested structure using naming convention
declare -A data
# user.name
data[user.name]="John Doe"
data[user.email]="john@example.com"
data[user.age]=30
# user.address.city
data[user.address.city]="New York"
data[user.address.country]="USA"
# settings.theme
data[settings.theme]="dark"
data[settings.language]="en"
# Access nested data
echo "Name: ${data[user.name]}"
echo "City: ${data[user.address.city]}"
echo "Theme: ${data[settings.theme]}"
# Get all user.address.* keys
for key in "${!data[@]}"; do
if [[ "$key" == user.address.* ]]; then
echo "$key = ${data[$key]}"
fi
done
3. 배열 패턴: 스택, 큐, 집합¶
스택 (LIFO - Last In, First Out)¶
#!/usr/bin/env bash
# Stack implementation using array
declare -a stack
# Push
push() {
stack+=("$1")
}
# Pop
pop() {
if [ ${#stack[@]} -eq 0 ]; then
return 1
fi
local value="${stack[-1]}"
unset 'stack[-1]'
echo "$value"
}
# Peek (view top without removing)
peek() {
if [ ${#stack[@]} -eq 0 ]; then
return 1
fi
echo "${stack[-1]}"
}
# Is empty
is_empty() {
[ ${#stack[@]} -eq 0 ]
}
# Size
size() {
echo "${#stack[@]}"
}
# Usage
push "first"
push "second"
push "third"
echo "Top: $(peek)" # third
echo "Size: $(size)" # 3
echo "Pop: $(pop)" # third
echo "Pop: $(pop)" # second
echo "Size: $(size)" # 1
큐 (FIFO - First In, First Out)¶
#!/usr/bin/env bash
# Queue implementation using array
declare -a queue
# Enqueue (add to end)
enqueue() {
queue+=("$1")
}
# Dequeue (remove from front)
dequeue() {
if [ ${#queue[@]} -eq 0 ]; then
return 1
fi
local value="${queue[0]}"
queue=("${queue[@]:1}") # Remove first element
echo "$value"
}
# Front (peek at first element)
front() {
if [ ${#queue[@]} -eq 0 ]; then
return 1
fi
echo "${queue[0]}"
}
# Usage
enqueue "job1"
enqueue "job2"
enqueue "job3"
echo "Front: $(front)" # job1
echo "Dequeue: $(dequeue)" # job1
echo "Dequeue: $(dequeue)" # job2
echo "Front: $(front)" # job3
집합 (고유 값)¶
#!/usr/bin/env bash
# Set implementation using associative array
declare -A set
# Add element
set_add() {
local value="$1"
set[$value]=1
}
# Remove element
set_remove() {
local value="$1"
unset "set[$value]"
}
# Contains
set_contains() {
local value="$1"
[[ -v set[$value] ]]
}
# Size
set_size() {
echo "${#set[@]}"
}
# Get all elements
set_elements() {
echo "${!set[@]}"
}
# Union
set_union() {
local -n set1=$1
local -n set2=$2
local -A result
for key in "${!set1[@]}"; do
result[$key]=1
done
for key in "${!set2[@]}"; do
result[$key]=1
done
echo "${!result[@]}"
}
# Intersection
set_intersection() {
local -n set1=$1
local -n set2=$2
local -A result
for key in "${!set1[@]}"; do
if [[ -v set2[$key] ]]; then
result[$key]=1
fi
done
echo "${!result[@]}"
}
# Usage
set_add "apple"
set_add "banana"
set_add "cherry"
set_add "apple" # Duplicate, ignored
echo "Size: $(set_size)" # 3
set_contains "banana" && echo "Contains banana"
echo "Elements: $(set_elements)"
# Set operations
declare -A set_a=(["a"]=1 ["b"]=1 ["c"]=1)
declare -A set_b=(["b"]=1 ["c"]=1 ["d"]=1)
echo "Union: $(set_union set_a set_b)" # a b c d
echo "Intersection: $(set_intersection set_a set_b)" # b c
4. CSV 파싱¶
CSV(Comma-Separated Values) 파일을 배열로 파싱합니다.
기본 CSV 리더¶
#!/usr/bin/env bash
# Read CSV into array
read_csv() {
local file="$1"
local -n rows_ref=$2
[ -f "$file" ] || return 1
local line
while IFS= read -r line; do
rows_ref+=("$line")
done < "$file"
}
# Parse CSV line into array
parse_csv_line() {
local line="$1"
local -n fields_ref=$2
local IFS=','
read -ra fields_ref <<< "$line"
}
# Example CSV file
cat > data.csv <<'EOF'
name,age,city
John,30,New York
Jane,25,Los Angeles
Bob,35,Chicago
EOF
# Read and parse
declare -a rows
read_csv "data.csv" rows
echo "Total rows: ${#rows[@]}"
for row in "${rows[@]}"; do
declare -a fields
parse_csv_line "$row" fields
echo "Fields: ${fields[@]}"
done
rm data.csv
고급 CSV 파서 (따옴표 처리)¶
#!/usr/bin/env bash
# Parse CSV with quoted fields
parse_csv_advanced() {
local line="$1"
local -n result=$2
result=()
local field=""
local in_quotes=false
local i char
for ((i=0; i<${#line}; i++)); do
char="${line:i:1}"
case "$char" in
'"')
if $in_quotes; then
# Check for escaped quote ""
if [[ "${line:i+1:1}" == '"' ]]; then
field+="$char"
((i++))
else
in_quotes=false
fi
else
in_quotes=true
fi
;;
',')
if $in_quotes; then
field+="$char"
else
result+=("$field")
field=""
fi
;;
*)
field+="$char"
;;
esac
done
# Add last field
result+=("$field")
}
# Test CSV with quotes
csv_line='John,"New York, NY","He said ""Hello"""'
declare -a fields
parse_csv_advanced "$csv_line" fields
echo "Number of fields: ${#fields[@]}"
for i in "${!fields[@]}"; do
echo "Field $i: ${fields[$i]}"
done
# Field 0: John
# Field 1: New York, NY
# Field 2: He said "Hello"
CSV 열 추출¶
#!/usr/bin/env bash
# Extract specific column from CSV
extract_column() {
local file="$1"
local column_index="$2"
local -n result=$3
[ -f "$file" ] || return 1
local line
while IFS=, read -ra fields; do
if [ ${#fields[@]} -gt $column_index ]; then
result+=("${fields[$column_index]}")
fi
done < "$file"
}
# Example
cat > employees.csv <<'EOF'
name,department,salary
John,Engineering,80000
Jane,Marketing,75000
Bob,Engineering,85000
Alice,Sales,70000
EOF
# Extract departments (column 1)
declare -a departments
extract_column "employees.csv" 1 departments
echo "Departments:"
for dept in "${departments[@]}"; do
echo " $dept"
done
rm employees.csv
CSV를 연관 배열로 변환¶
#!/usr/bin/env bash
# Load CSV into array of associative arrays
load_csv_records() {
local file="$1"
local -n records_ref=$2
[ -f "$file" ] || return 1
# Read header
local header
IFS= read -r header < "$file"
# Parse header
local IFS=','
read -ra headers <<< "$header"
# Read data rows
local line row_num=0
while IFS= read -r line; do
# Skip header
[ $row_num -eq 0 ] && { ((row_num++)); continue; }
# Parse row
local IFS=','
read -ra values <<< "$line"
# Create record name
local record_prefix="record_${row_num}"
# Store each field
for i in "${!headers[@]}"; do
local key="${record_prefix}_${headers[$i]}"
records_ref[$key]="${values[$i]}"
done
((row_num++))
done < "$file"
# Return number of records
echo $((row_num - 1))
}
# Example
cat > users.csv <<'EOF'
name,email,role
John,john@example.com,admin
Jane,jane@example.com,user
Bob,bob@example.com,user
EOF
declare -A records
num_records=$(load_csv_records "users.csv" records)
echo "Loaded $num_records records"
# Access records
for i in $(seq 1 $num_records); do
echo "Record $i:"
echo " Name: ${records[record_${i}_name]}"
echo " Email: ${records[record_${i}_email]}"
echo " Role: ${records[record_${i}_role]}"
done
rm users.csv
5. 설정 파일 로딩¶
간단한 키-값 설정¶
#!/usr/bin/env bash
# Load key=value config file
load_config() {
local config_file="$1"
local -n config_ref=$2
[ -f "$config_file" ] || return 1
local line key value
while IFS= read -r line; do
# Skip empty lines and comments
[[ "$line" =~ ^[[:space:]]*$ ]] && continue
[[ "$line" =~ ^[[:space:]]*# ]] && continue
# Remove inline comments
line="${line%%#*}"
# Trim whitespace
line="${line#"${line%%[![:space:]]*}"}"
line="${line%"${line##*[![:space:]]}"}"
# Must contain =
[[ "$line" != *=* ]] && continue
# Parse key=value
key="${line%%=*}"
value="${line#*=}"
# Trim key and value
key="${key#"${key%%[![:space:]]*}"}"
key="${key%"${key##*[![:space:]]}"}"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
# Remove quotes from value
if [[ "$value" =~ ^\".*\"$ ]] || [[ "$value" =~ ^\'.*\'$ ]]; then
value="${value:1:-1}"
fi
config_ref["$key"]="$value"
done < "$config_file"
}
# Example config file
cat > app.conf <<'EOF'
# Application configuration
host = localhost
port = 8080
database = "myapp"
# Feature flags
debug = true
verbose = false # Verbose logging
EOF
declare -A config
load_config "app.conf" config
echo "Configuration:"
for key in "${!config[@]}"; do
echo " $key = ${config[$key]}"
done
rm app.conf
섹션이 있는 INI 스타일 설정¶
#!/usr/bin/env bash
# Load INI-style config with sections
load_ini_config() {
local config_file="$1"
local -n config_ref=$2
[ -f "$config_file" ] || return 1
local section=""
local line key value
while IFS= read -r line; do
# Skip empty and comments
[[ "$line" =~ ^[[:space:]]*$ ]] && continue
[[ "$line" =~ ^[[:space:]]*[#\;] ]] && continue
# Trim whitespace
line="${line#"${line%%[![:space:]]*}"}"
line="${line%"${line##*[![:space:]]}"}"
# Section header
if [[ "$line" =~ ^\[.*\]$ ]]; then
section="${line:1:-1}"
continue
fi
# Key=value
if [[ "$line" == *=* ]]; then
key="${line%%=*}"
value="${line#*=}"
# Trim
key="${key#"${key%%[![:space:]]*}"}"
key="${key%"${key##*[![:space:]]}"}"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
# Remove quotes
if [[ "$value" =~ ^\".*\"$ ]] || [[ "$value" =~ ^\'.*\'$ ]]; then
value="${value:1:-1}"
fi
# Store with section prefix
if [ -n "$section" ]; then
config_ref["${section}.${key}"]="$value"
else
config_ref["$key"]="$value"
fi
fi
done < "$config_file"
}
# Example INI file
cat > config.ini <<'EOF'
; Global settings
timeout = 30
[database]
host = localhost
port = 5432
name = myapp
[cache]
host = localhost
port = 6379
ttl = 3600
EOF
declare -A config
load_ini_config "config.ini" config
echo "Configuration:"
for key in $(echo "${!config[@]}" | tr ' ' '\n' | sort); do
echo " $key = ${config[$key]}"
done
# Access specific sections
echo -e "\nDatabase config:"
for key in "${!config[@]}"; do
if [[ "$key" == database.* ]]; then
echo " ${key#database.} = ${config[$key]}"
fi
done
rm config.ini
기본값이 있는 설정¶
#!/usr/bin/env bash
# Config loader with default values
declare -A default_config=(
[host]="localhost"
[port]="8080"
[debug]="false"
[max_connections]="100"
[timeout]="30"
)
load_config_with_defaults() {
local config_file="$1"
local -n config_ref=$2
local -n defaults_ref=$3
# Start with defaults
for key in "${!defaults_ref[@]}"; do
config_ref["$key"]="${defaults_ref[$key]}"
done
# Load config file if exists
if [ -f "$config_file" ]; then
local line key value
while IFS= read -r line; do
[[ "$line" =~ ^[[:space:]]*$ ]] && continue
[[ "$line" =~ ^[[:space:]]*# ]] && continue
line="${line%%#*}"
[[ "$line" != *=* ]] && continue
key="${line%%=*}"
value="${line#*=}"
key="${key#"${key%%[![:space:]]*}"}"
key="${key%"${key##*[![:space:]]}"}"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
[[ "$value" =~ ^[\"\'].*[\"\']$ ]] && value="${value:1:-1}"
config_ref["$key"]="$value"
done < "$config_file"
fi
}
# Test with partial config
cat > partial.conf <<'EOF'
host = production.example.com
debug = true
EOF
declare -A config
load_config_with_defaults "partial.conf" config default_config
echo "Final configuration:"
for key in "${!config[@]}"; do
echo " $key = ${config[$key]}"
done
rm partial.conf
6. 다차원 데이터¶
Bash는 기본적으로 다차원 배열을 지원하지 않지만 시뮬레이션할 수 있습니다.
2차원 배열 시뮬레이션¶
#!/usr/bin/env bash
# Simulate 2D array using naming convention: arr_row_col
declare -A matrix
# Set value at [row][col]
matrix_set() {
local row=$1
local col=$2
local value=$3
matrix["${row}_${col}"]="$value"
}
# Get value at [row][col]
matrix_get() {
local row=$1
local col=$2
echo "${matrix["${row}_${col}"]}"
}
# Create 3x3 matrix
matrix_set 0 0 1
matrix_set 0 1 2
matrix_set 0 2 3
matrix_set 1 0 4
matrix_set 1 1 5
matrix_set 1 2 6
matrix_set 2 0 7
matrix_set 2 1 8
matrix_set 2 2 9
# Print matrix
echo "Matrix:"
for row in 0 1 2; do
for col in 0 1 2; do
printf "%3s" "$(matrix_get $row $col)"
done
echo
done
테이블 데이터 구조¶
#!/usr/bin/env bash
# Table with named columns
declare -A table
declare -a columns=("name" "age" "city")
declare -i row_count=0
# Add row
table_add_row() {
local name="$1"
local age="$2"
local city="$3"
table["${row_count}_name"]="$name"
table["${row_count}_age"]="$age"
table["${row_count}_city"]="$city"
((row_count++))
}
# Get cell value
table_get() {
local row=$1
local col=$2
echo "${table["${row}_${col}"]}"
}
# Print table
table_print() {
# Header
printf "%-15s %-5s %-20s\n" "${columns[@]}"
printf "%-15s %-5s %-20s\n" "---------------" "-----" "--------------------"
# Rows
for ((i=0; i<row_count; i++)); do
printf "%-15s %-5s %-20s\n" \
"$(table_get $i name)" \
"$(table_get $i age)" \
"$(table_get $i city)"
done
}
# Usage
table_add_row "John Doe" 30 "New York"
table_add_row "Jane Smith" 25 "Los Angeles"
table_add_row "Bob Johnson" 35 "Chicago"
table_print
declare -p를 사용한 직렬화¶
#!/usr/bin/env bash
# Save array to file
save_array() {
local array_name="$1"
local file="$2"
declare -p "$array_name" > "$file"
}
# Load array from file
load_array() {
local file="$1"
[ -f "$file" ] || return 1
source "$file"
}
# Example with indexed array
my_array=(apple banana cherry)
save_array "my_array" "array.dat"
# Clear array
unset my_array
# Reload
load_array "array.dat"
echo "Restored: ${my_array[@]}"
# Example with associative array
declare -A my_map=([host]=localhost [port]=8080)
save_array "my_map" "map.dat"
# Clear and reload
unset my_map
load_array "map.dat"
echo "Restored: ${!my_map[@]}"
rm array.dat map.dat
7. 실용 패턴¶
인수 수집¶
#!/usr/bin/env bash
# Collect arguments into arrays
declare -a files
declare -a options
declare -A flags
while [[ $# -gt 0 ]]; do
case "$1" in
-v|--verbose)
flags[verbose]=1
shift
;;
-o|--output)
flags[output]="$2"
shift 2
;;
--option=*)
option="${1#*=}"
options+=("$option")
shift
;;
-*)
echo "Unknown option: $1" >&2
exit 1
;;
*)
files+=("$1")
shift
;;
esac
done
echo "Files: ${files[@]}"
echo "Options: ${options[@]}"
echo "Verbose: ${flags[verbose]:-0}"
echo "Output: ${flags[output]:-stdout}"
동적으로 명령 구성¶
#!/usr/bin/env bash
# Build command with options
build_command() {
local -a cmd=(rsync -av)
# Add optional flags
[ -n "$DRY_RUN" ] && cmd+=(--dry-run)
[ -n "$DELETE" ] && cmd+=(--delete)
[ -n "$COMPRESS" ] && cmd+=(--compress)
# Add exclude patterns
local -a excludes=(
"*.tmp"
"*.log"
".git"
)
for pattern in "${excludes[@]}"; do
cmd+=(--exclude="$pattern")
done
# Add source and destination
cmd+=("$SOURCE" "$DEST")
# Execute
echo "Running: ${cmd[@]}"
"${cmd[@]}"
}
# Usage
SOURCE="/data"
DEST="/backup"
DRY_RUN=1
DELETE=1
build_command
안전한 단어 분할¶
#!/usr/bin/env bash
# UNSAFE: word splitting on spaces
unsafe_cmd="ls -la /tmp"
$unsafe_cmd # Vulnerable to injection
# SAFE: use array
safe_cmd=(ls -la /tmp)
"${safe_cmd[@]}"
# Building complex commands
build_find_command() {
local dir="$1"
shift
local -a cmd=(find "$dir")
# Add search criteria
while [[ $# -gt 0 ]]; do
case "$1" in
--name)
cmd+=(-name "$2")
shift 2
;;
--type)
cmd+=(-type "$2")
shift 2
;;
--newer)
cmd+=(-newer "$2")
shift 2
;;
*)
shift
;;
esac
done
# Execute safely
"${cmd[@]}"
}
# Usage
build_find_command "/var/log" --type f --name "*.log"
연습 문제¶
문제 1: 고급 CSV 처리기¶
다음 기능을 가진 CSV 처리 도구를 만드세요: - 따옴표가 있는 필드와 이스케이프된 따옴표가 있는 CSV 파싱 - 열 값에 기반한 행 필터링 (정규식 지원) - 지정된 열로 정렬 (여러 열, 오름차순/내림차순) - 집계 함수 계산 (sum, avg, min, max, count) - 공통 열을 기준으로 두 CSV 파일 조인 - CSV 또는 JSON 형식으로 결과 내보내기
예제:
csv_tool data.csv \
--filter "age > 25" \
--filter "city =~ ^New" \
--sort "age:desc,name:asc" \
--aggregate "avg(salary)" \
--output result.csv
문제 2: 설정 관리자¶
다음 기능을 가진 설정 관리 시스템을 구축하세요: - 여러 소스에서 설정 로드 (파일, 환경, CLI 인수) - 계층화된 설정 지원 (기본값 < 시스템 < 사용자 < 환경) - 스키마에 대한 설정 검증 (필수 필드, 타입, 범위) - get/set/list 연산 제공 - 다양한 형식으로 설정 내보내기 (INI, JSON, YAML-like) - 설정 파일 변경 감시
예제:
config load defaults.conf system.conf user.conf
config set database.host localhost
config get database.port # Returns with fallback to defaults
config validate # Check all required fields
config export --format json > config.json
문제 3: 데이터 구조 라이브러리¶
다음 데이터 구조들의 라이브러리를 구현하세요: - 스택(Stack): push, pop, peek, size, clear - 큐(Queue): enqueue, dequeue, front, back, size - 우선순위 큐(Priority Queue): insert with priority, extract_max - 집합(Set): add, remove, contains, union, intersection, difference - 맵(Map): put, get, remove, keys, values, entries - 리스트(List): append, prepend, insert, remove, get, size
각 구조에 대한 영속성(파일로 저장/로드) 포함하세요.
문제 4: 테이블 데이터 처리기¶
다음 기능을 가진 테이블 조작 도구를 만드세요: - CSV/TSV/고정 너비 파일에서 데이터 로드 - 열 추가/제거/이름 변경 - 복잡한 조건으로 행 필터링 - 값 변환 (열에 대한 맵 함수) - 집계와 함께 열별로 그룹화 - 피벗/언피벗 연산 - 다양한 형식으로 내보내기
예제:
table load employees.csv
table filter 'salary > 50000'
table group_by department --aggregate 'avg(salary),count(*)'
table sort salary:desc
table select name,department,salary
table export --format markdown
문제 5: 로그 집계기¶
다음 기능을 가진 로그 집계 도구를 작성하세요: - 여러 파일에서 로그 파싱 (다양한 형식) - 구조화된 데이터 추출 (타임스탬프, 레벨, 메시지, 메타데이터) - 검색 가능한 데이터 구조에 저장 - 시간 범위, 레벨, 패턴으로 필터링 - 다양한 필드별로 그룹화 및 카운트 - 통계 생성 (오류율, 패턴, 상위 오류) - 다양한 형식으로 보고서 내보내기
예제:
log_agg --input "*.log" \
--format apache \
--time-range "2024-02-13 00:00 to 2024-02-13 23:59" \
--filter "level=ERROR" \
--group-by hour \
--top errors 10 \
--output report.html
이전: 02_Parameter_Expansion.md | 다음: 04_Advanced_Control_Flow.md