9 분 소요

개요

배열과 문자열 연산은 복잡한 데이터를 효과적으로 처리하는 핵심 기능입니다. 이 문서에서는 배열 선언, 요소 추가/삭제/검색, 문자열 길이 계산, 치환, 슬라이싱 등 실무에서 자주 사용하는 고급 기법을 학습합니다.

1. 배열 (Arrays)

1.1 배열이란?

배열은 여러 값을 하나의 변수에 저장할 수 있는 자료구조입니다.

배열의 특징:

  • 인덱스는 0부터 시작
  • 크기가 고정되지 않음 (동적 확장 가능)
  • 문자열과 숫자를 혼합하여 저장 가능

1.2 배열 선언 및 초기화

#!/bin/bash

# 방법 1: 괄호 안에 값 나열
arr=(10 20 30 "it" "itschool")

# 방법 2: 인덱스 지정
fruits[0]="apple"
fruits[1]="banana"
fruits[2]="cherry"

# 방법 3: 명령어 실행 결과를 배열로 저장
files=($(ls))

# 방법 4: 빈 배열 선언
empty_arr=()

1.3 배열 요소 접근

#!/bin/bash

arr=(10 20 30 "it" "itschool")

# 첫 번째 요소 (인덱스 지정 안하면 첫 번째만 출력)
echo ${arr}           # 10
echo ${arr[0]}        # 10

# 특정 인덱스 요소
echo ${arr[1]}        # 20
echo ${arr[3]}        # it

# 모든 요소 출력
echo ${arr[@]}        # 10 20 30 it itschool
echo ${arr[*]}        # 10 20 30 it itschool

# 배열 길이 (요소 개수)
echo ${#arr[@]}       # 5

# 특정 요소의 길이
echo ${#arr[4]}       # 8 (itschool의 길이)

${arr[@]}와 ${arr[*]}의 차이:

#!/bin/bash

arr=("apple pie" "banana" "cherry")

# [@]는 각 요소를 개별 인자로
for item in "${arr[@]}"
do
    echo "- $item"
done
# 출력:
# - apple pie
# - banana
# - cherry

# [*]는 하나의 문자열로
for item in "${arr[*]}"
do
    echo "- $item"
done
# 출력:
# - apple pie banana cherry

1.4 배열 요소 추가

#!/bin/bash

# 초기 배열
arr=(10 20 30)

# 방법 1: 특정 인덱스에 할당
arr[3]=40
echo ${arr[@]}  # 10 20 30 40

# 방법 2: += 연산자로 추가
arr+=(50)
echo ${arr[@]}  # 10 20 30 40 50

# 방법 3: 여러 요소 동시 추가
arr+=(60 70 80)
echo ${arr[@]}  # 10 20 30 40 50 60 70 80

변수를 배열에 추가:

#!/bin/bash

fruits=("apple" "banana")
new_fruit="cherry"

# 하나의 요소로 추가하려면 큰따옴표로 감싸기
fruits+=("$new_fruit")
echo ${fruits[@]}  # apple banana cherry

1.5 배열 요소 삭제

#!/bin/bash

arr=(10 20 30 40 50)

# 특정 인덱스 삭제
unset 'arr[1]'
echo ${arr[@]}     # 10 30 40 50
echo ${!arr[@]}    # 0 2 3 4 (인덱스 확인)

# 전체 배열 삭제
unset arr

1.6 배열 인덱스 확인

#!/bin/bash

arr=(10 20 30)
arr[10]=100  # 중간 인덱스 건너뛰기

# 모든 인덱스 출력
echo ${!arr[@]}  # 0 1 2 10

1.7 배열 슬라이싱 (일부 추출)

#!/bin/bash

arr=(10 20 30 40 50 60 70)

# 형식: ${배열[@]:시작인덱스:개수}

# 인덱스 2부터 3개 추출
echo ${arr[@]:2:3}  # 30 40 50

# 인덱스 4부터 끝까지
echo ${arr[@]:4}    # 50 60 70

1.8 배열 합치기

#!/bin/bash

arr1=(10 20 30)
arr2=(40 50)

# 두 배열 합치기
arr3=(${arr1[@]} ${arr2[@]})
echo ${arr3[@]}  # 10 20 30 40 50

1.9 배열에서 특정 값 찾기 및 숨기기

#!/bin/bash

arr=(10 20 30 20 40 20 50)

# 특정 값을 빈 문자열로 치환하여 숨기기
echo ${arr[@]/20}       # 10 30 40 50 (첫 번째 20만 제거)
echo ${arr[@]//20}      # 10 30 40 50 (모든 20 제거)

# 원본 배열은 변경되지 않음
echo ${arr[@]}          # 10 20 30 20 40 20 50

2. 연관 배열 (Associative Arrays)

2.1 연관 배열 선언

연관 배열은 Key-Value 쌍으로 데이터를 저장합니다.

#!/bin/bash

# 연관 배열 선언
declare -A person

# 값 할당
person[name]="홍길동"
person[age]=30
person[city]="서울"

# 값 접근
echo ${person[name]}    # 홍길동
echo ${person[age]}     # 30

# 모든 key 출력
echo ${!person[@]}      # name age city

# 모든 value 출력
echo ${person[@]}       # 홍길동 30 서울

2.2 연관 배열 활용 예제

#!/bin/bash

# 서버 정보 관리
declare -A servers

servers[web1]="192.168.1.10"
servers[web2]="192.168.1.11"
servers[db1]="192.168.1.20"

# 모든 서버 정보 출력
for server in "${!servers[@]}"
do
    echo "$server: ${servers[$server]}"
done

# 출력:
# web1: 192.168.1.10
# web2: 192.168.1.11
# db1: 192.168.1.20

3. 문자열 연산 (String Operations)

3.1 문자열 길이

#!/bin/bash

myVar="Hello World"

# 문자열 길이
echo ${#myVar}  # 11

3.2 대소문자 변환

#!/bin/bash

myVar="How are you? Fine thanks"

# 대문자로 변환
echo ${myVar^^}      # HOW ARE YOU? FINE THANKS

# 소문자로 변환
echo ${myVar,,}      # how are you? fine thanks

# 첫 글자만 대문자
echo ${myVar^}       # How are you? fine thanks

# 첫 글자만 소문자
echo ${myVar,}       # how are you? Fine thanks

3.3 문자열 치환 (Replace)

#!/bin/bash

myVar="How are you? Fine thanks"

# 첫 번째 일치하는 문자열만 치환
echo ${myVar/Fine/So}
# 출력: How are you? So thanks

# 모든 일치하는 문자열 치환
text="old/old/new"
echo ${text//old/new}
# 출력: new/new/new

특정 패턴 제거:

#!/bin/bash

path="/home/user/documents/file.txt"

# .txt 제거
echo ${path/.txt/}     # /home/user/documents/file

# /old/ 를 /new/ 로 변경
echo ${path/old/new}   # /home/user/documents/file.txt

3.4 문자열 슬라이싱 (Substring)

#!/bin/bash

myVar="How are you?"

# 형식: ${변수:시작위치:길이}

# 4번째 인덱스부터 8개 문자
echo ${myVar:4:8}      # are you?

# 4번째 인덱스부터 끝까지
echo ${myVar:4}        # are you?

# 뒤에서부터 (음수 사용)
echo ${myVar: -4}      # you?
echo ${myVar: -6:2}    # e

3.5 문자열 시작/끝 패턴 제거

#!/bin/bash

filename="document.tar.gz"

# 앞에서부터 제거 (#: 최소 매칭, ##: 최대 매칭)
echo ${filename#*.}     # tar.gz (첫 번째 . 부터 제거)
echo ${filename##*.}    # gz (마지막 . 부터 제거)

# 뒤에서부터 제거 (%: 최소 매칭, %%: 최대 매칭)
echo ${filename%.*}     # document.tar (마지막 . 제거)
echo ${filename%%.*}    # document (모든 . 제거)

경로에서 파일명/디렉토리명 추출:

#!/bin/bash

path="/home/user/documents/file.txt"

# 파일명만 추출 (dirname 제거)
filename=${path##*/}
echo $filename         # file.txt

# 디렉토리명만 추출 (filename 제거)
dirname=${path%/*}
echo $dirname          # /home/user/documents

# 확장자 제거
basename=${filename%.*}
echo $basename         # file

# 확장자만 추출
extension=${filename##*.}
echo $extension        # txt

3.6 문자열 비교

#!/bin/bash

str1="hello"
str2="world"

# 같은지 비교
if [ "$str1" = "$str2" ]; then
    echo "같습니다"
else
    echo "다릅니다"
fi

# 빈 문자열 확인
if [ -z "$str1" ]; then
    echo "빈 문자열"
else
    echo "값이 있음"
fi

# 문자열 길이 확인
if [ -n "$str1" ]; then
    echo "값이 있음"
else
    echo "빈 문자열"
fi

3.7 문자열 포함 여부 확인

#!/bin/bash

text="Hello World"

# 특정 문자열 포함 여부
if [[ "$text" == *"World"* ]]; then
    echo "World를 포함합니다"
fi

# 시작 문자열 확인
if [[ "$text" == "Hello"* ]]; then
    echo "Hello로 시작합니다"
fi

# 끝 문자열 확인
if [[ "$text" == *"World" ]]; then
    echo "World로 끝납니다"
fi

4. 실전 예제

예제 1: 로그 파일 분석

#!/bin/bash

# 로그 파일을 배열로 읽기
mapfile -t log_lines < /var/log/syslog

echo "총 로그 라인 수: ${#log_lines[@]}"

# 최근 10개 로그만 출력
echo "최근 10개 로그:"
for line in "${log_lines[@]: -10}"
do
    echo "$line"
done

예제 2: CSV 파일 처리

#!/bin/bash

# CSV 파일: name,age,city
# 홍길동,30,서울
# 김철수,25,부산

while IFS=',' read -r name age city
do
    echo "이름: $name, 나이: $age, 도시: $city"
done < users.csv

예제 3: 파일 확장자별 개수 세기

#!/bin/bash

declare -A extensions

# 현재 디렉토리의 모든 파일 처리
for file in *
do
    if [ -f "$file" ]; then
        # 확장자 추출
        ext="${file##*.}"

        # 확장자가 없으면 "no_ext"
        if [ "$ext" = "$file" ]; then
            ext="no_ext"
        fi

        # 개수 증가
        ((extensions[$ext]++))
    fi
done

# 결과 출력
echo "=== 파일 확장자별 개수 ==="
for ext in "${!extensions[@]}"
do
    echo "$ext: ${extensions[$ext]}개"
done

예제 4: 사용자 입력 검증

#!/bin/bash

validate_email() {
    local email=$1

    # 이메일 형식 검증 (간단한 버전)
    if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
        echo "유효한 이메일입니다: $email"
        return 0
    else
        echo "유효하지 않은 이메일입니다: $email"
        return 1
    fi
}

read -p "이메일을 입력하세요: " email
validate_email "$email"

예제 5: 파일명 일괄 변경

#!/bin/bash

# .jpg 파일을 .jpeg로 일괄 변경

count=0

for file in *.jpg
do
    if [ -f "$file" ]; then
        # 확장자 변경
        new_name="${file%.jpg}.jpeg"

        mv "$file" "$new_name"
        echo "변경: $file -> $new_name"
        ((count++))
    fi
done

echo "총 ${count}개 파일 변경 완료"

예제 6: IP 주소 유효성 검증

#!/bin/bash

validate_ip() {
    local ip=$1
    local valid_ip_regex='^([0-9]{1,3}\.){3}[0-9]{1,3}$'

    if [[ $ip =~ $valid_ip_regex ]]; then
        # 각 옥텟이 0-255 범위인지 확인
        IFS='.' read -ra ADDR <<< "$ip"
        for i in "${ADDR[@]}"
        do
            if [ $i -gt 255 ]; then
                echo "유효하지 않은 IP: $ip"
                return 1
            fi
        done
        echo "유효한 IP: $ip"
        return 0
    else
        echo "유효하지 않은 IP 형식: $ip"
        return 1
    fi
}

validate_ip "192.168.1.1"
validate_ip "256.1.1.1"
validate_ip "abc.def.ghi.jkl"

예제 7: 서버 목록 관리

#!/bin/bash

# 서버 정보를 연관 배열로 관리
declare -A servers

servers=(
    [web1]="192.168.1.10:80:running"
    [web2]="192.168.1.11:80:stopped"
    [db1]="192.168.1.20:3306:running"
)

# 서버 상태 출력
echo "=== 서버 상태 ==="
for server in "${!servers[@]}"
do
    IFS=':' read -ra info <<< "${servers[$server]}"
    ip="${info[0]}"
    port="${info[1]}"
    status="${info[2]}"

    echo "[$server]"
    echo "  IP: $ip"
    echo "  Port: $port"
    echo "  Status: $status"
    echo ""
done

5. 고급 기법

5.1 배열 정렬

#!/bin/bash

arr=(50 20 80 10 40)

# 배열 정렬 (오름차순)
IFS=$'\n' sorted=($(sort -n <<<"${arr[*]}"))
unset IFS

echo "정렬 전: ${arr[@]}"
echo "정렬 후: ${sorted[@]}"

5.2 배열 중복 제거

#!/bin/bash

arr=(10 20 30 20 40 10 50)

# 중복 제거
unique=($(echo "${arr[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))

echo "원본: ${arr[@]}"
echo "중복 제거: ${unique[@]}"

5.3 문자열 분할 (Split)

#!/bin/bash

# IFS(Internal Field Separator)를 이용한 문자열 분할
text="apple,banana,cherry"

IFS=',' read -ra fruits <<< "$text"

for fruit in "${fruits[@]}"
do
    echo "과일: $fruit"
done

5.4 문자열 결합 (Join)

#!/bin/bash

arr=("apple" "banana" "cherry")

# 배열을 문자열로 결합
IFS=','
joined="${arr[*]}"
unset IFS

echo "$joined"  # apple,banana,cherry

5.5 다차원 배열 시뮬레이션

#!/bin/bash

# bash는 진정한 다차원 배열을 지원하지 않지만 시뮬레이션 가능

# 2D 배열 시뮬레이션
matrix[0,0]=1
matrix[0,1]=2
matrix[1,0]=3
matrix[1,1]=4

# 접근
echo ${matrix[0,0]}  # 1
echo ${matrix[1,1]}  # 4

# 반복문으로 출력
for i in 0 1
do
    for j in 0 1
    do
        echo -n "${matrix[$i,$j]} "
    done
    echo ""
done
# 출력:
# 1 2
# 3 4

6. 종합 예제: 시스템 정보 수집 스크립트

#!/bin/bash

# 시스템 정보를 배열과 연관 배열로 수집

echo "=== 시스템 정보 수집 ==="

# 1. 마운트된 파일시스템 목록 (배열)
mapfile -t filesystems < <(df -h | tail -n +2 | awk '{print $1}')

echo "마운트된 파일시스템 개수: ${#filesystems[@]}"
for fs in "${filesystems[@]}"
do
    echo "  - $fs"
done

echo ""

# 2. 사용자 정보 (연관 배열)
declare -A user_info

while IFS=: read -r username _ uid _ _ home shell
do
    if [ $uid -ge 1000 ] && [ $uid -lt 60000 ]; then
        user_info["$username"]="UID:$uid,Home:$home,Shell:${shell##*/}"
    fi
done < /etc/passwd

echo "일반 사용자 개수: ${#user_info[@]}"
for user in "${!user_info[@]}"
do
    IFS=',' read -ra info <<< "${user_info[$user]}"
    echo "[$user]"
    for item in "${info[@]}"
    do
        echo "  $item"
    done
done

echo ""

# 3. 네트워크 인터페이스 정보
declare -A interfaces

while read -r line
do
    if [[ $line =~ ^[0-9]+:\ ([^:]+): ]]; then
        interface="${BASH_REMATCH[1]}"
        # lo와 docker 인터페이스 제외
        if [[ ! "$interface" =~ ^(lo|docker) ]]; then
            # IP 주소 가져오기
            ip_addr=$(ip addr show "$interface" | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -1)
            interfaces["$interface"]="${ip_addr:-No IP}"
        fi
    fi
done < <(ip link show)

echo "네트워크 인터페이스:"
for iface in "${!interfaces[@]}"
do
    echo "  $iface: ${interfaces[$iface]}"
done

주요 개념 요약

배열 관련

항목 문법 예시
배열 선언 arr=(값1 값2 ...) arr=(10 20 30)
요소 접근 ${arr[인덱스]} ${arr[0]}
전체 요소 ${arr[@]} ${arr[@]}
배열 길이 ${#arr[@]} ${#arr[@]}
요소 추가 arr+=(값) arr+=(40)
요소 삭제 unset 'arr[인덱스]' unset 'arr[1]'
인덱스 확인 ${!arr[@]} ${!arr[@]}
슬라이싱 ${arr[@]:시작:개수} ${arr[@]:2:3}

문자열 관련

항목 문법 예시
길이 ${#변수} ${#myVar}
대문자 ${변수^^} ${myVar^^}
소문자 ${변수,,} ${myVar,,}
치환 ${변수/old/new} ${myVar/Fine/So}
슬라이싱 ${변수:시작:길이} ${myVar:4:8}
앞에서 제거 ${변수#패턴} ${file#*.}
뒤에서 제거 ${변수%패턴} ${file%.*}

학습 포인트

1. 배열 사용 시기

  • 여러 관련 데이터를 하나로 그룹화
  • 반복 처리가 필요한 데이터
  • 동적으로 크기가 변하는 데이터

2. 연관 배열 활용

  • Key-Value 쌍으로 데이터 관리
  • 설정 정보, 서버 목록 등에 유용

3. 문자열 처리 최적화

  • 외부 명령어(sed, awk) 대신 내장 기능 사용
  • 성능과 가독성 향상

4. 실무 팁

배열 안전하게 순회하기:

# 좋은 예 (공백이 포함된 요소 처리)
for item in "${arr[@]}"
do
    echo "$item"
done

# 나쁜 예 (공백이 포함된 요소 분리됨)
for item in ${arr[@]}
do
    echo "$item"
done

빈 배열 확인:

if [ ${#arr[@]} -eq 0 ]; then
    echo "배열이 비어있습니다"
fi

파일을 배열로 읽기:

# 방법 1: mapfile (Bash 4.0+)
mapfile -t lines < file.txt

# 방법 2: 전통적인 방법
while IFS= read -r line
do
    lines+=("$line")
done < file.txt

마무리

배열과 문자열 연산은 쉘 스크립트의 데이터 처리 능력을 크게 향상시킵니다. 배열을 사용하면 여러 값을 효율적으로 관리할 수 있고, 문자열 연산을 마스터하면 외부 명령어 없이도 대부분의 텍스트 처리를 수행할 수 있습니다.

특히 연관 배열은 복잡한 설정 정보나 서버 관리에 매우 유용하며, 문자열 슬라이싱과 치환은 경로 처리, 파일명 변경 등 실무에서 자주 사용됩니다.

다음 Part에서는 Windows 배치 스크립트를 학습하여 Windows 환경에서의 자동화 방법을 배웁니다.

댓글 남기기