Lesson 07: 문자열 처리와 텍스트 조작

Lesson 07: 문자열 처리와 텍스트 조작

난이도: ⭐⭐⭐

이전: 06_IO_and_Redirection.md | 다음: 08_Regex_in_Bash.md


1. 내장 문자열 연산 복습

외부 도구를 탐색하기 전에, bash의 강력한 내장 문자열 연산을 복습해보겠습니다.

1.1 매개변수 확장(Parameter Expansion) 빠른 참조

#!/bin/bash

text="Hello World"

# 길이
echo "${#text}"  # 출력: 11

# 부분 문자열 추출
echo "${text:0:5}"   # 출력: Hello
echo "${text:6}"     # 출력: World
echo "${text: -5}"   # 출력: World (- 앞의 공백 주의)

# 시작부터 제거 (최단 일치)
filename="path/to/file.txt"
echo "${filename#*/}"    # 출력: to/file.txt

# 시작부터 제거 (최장 일치)
echo "${filename##*/}"   # 출력: file.txt

# 끝에서 제거 (최단 일치)
echo "${filename%.*}"    # 출력: path/to/file

# 끝에서 제거 (최장 일치)
echo "${filename%%/*}"   # 출력: path

1.2 문자열 치환

#!/bin/bash

text="foo bar foo baz foo"

# 첫 번째 일치 치환
echo "${text/foo/FOO}"    # 출력: FOO bar foo baz foo

# 모든 일치 치환
echo "${text//foo/FOO}"   # 출력: FOO bar FOO baz FOO

# 첫 번째 일치 제거
echo "${text/foo}"        # 출력:  bar foo baz foo

# 모든 일치 제거
echo "${text//foo}"       # 출력:  bar  baz

# 시작 부분 치환
echo "${text/#foo/START}" # 출력: START bar foo baz foo

# 끝 부분 치환
text2="foo bar foo"
echo "${text2/%foo/END}"  # 출력: foo bar END

1.3 대소문자 변환

#!/bin/bash

text="Hello World"

# 소문자로 변환
echo "${text,,}"          # 출력: hello world
echo "${text,}"           # 출력: hello World (첫 문자만)

# 대문자로 변환
echo "${text^^}"          # 출력: HELLO WORLD
echo "${text^}"           # 출력: Hello World (첫 문자만)

# 대소문자 토글 (첫 문자)
echo "${text~}"

# 대소문자 토글 (모든 문자)
echo "${text~~}"

1.4 문자열 연결과 반복

#!/bin/bash

# 연결
first="Hello"
last="World"
full="$first $last"
echo "$full"  # 출력: Hello World

# 변수에 추가
message="Hello"
message+=" World"
echo "$message"  # 출력: Hello World

# 문자열 반복 (printf 사용)
repeat_string() {
    local string=$1
    local count=$2
    printf "%${count}s" | tr ' ' "$string"
}

echo "$(repeat_string '=' 40)"  # 출력: ========================================

# 대안: bash 루프 사용
repeat_string2() {
    local string=$1
    local count=$2
    local result=""
    for ((i=0; i<count; i++)); do
        result+="$string"
    done
    echo "$result"
}

echo "$(repeat_string2 '-' 20)"  # 출력: --------------------

1.5 문자열 비교

#!/bin/bash

str1="hello"
str2="world"

# 동등
[[ $str1 == $str2 ]] && echo "Equal" || echo "Not equal"

# 부등
[[ $str1 != $str2 ]] && echo "Different"

# 사전순 비교
[[ $str1 < $str2 ]] && echo "$str1 comes before $str2"
[[ $str1 > $str2 ]] && echo "$str1 comes after $str2"

# 비어있는지 확인
[[ -z $str1 ]] && echo "Empty" || echo "Not empty"

# 비어있지 않은지 확인
[[ -n $str1 ]] && echo "Not empty"

# 패턴 매칭
[[ $str1 == h* ]] && echo "Starts with h"
[[ $str1 == *o ]] && echo "Ends with o"

2. printf 포매팅

printf 명령은 강력한 문자열 포매팅 기능을 제공합니다.

2.1 기본 형식 지정자

#!/bin/bash

# 문자열
printf "%s\n" "Hello World"

# 정수
printf "%d\n" 42

# 부동소수점
printf "%f\n" 3.14159
printf "%.2f\n" 3.14159  # 출력: 3.14

# 16진수
printf "%x\n" 255   # 출력: ff
printf "%X\n" 255   # 출력: FF

# 8진수
printf "%o\n" 64    # 출력: 100

# 문자 (ASCII)
printf "%c\n" 65    # 출력: A

2.2 너비와 정밀도

#!/bin/bash

# 최소 너비 (오른쪽 정렬)
printf "%10s\n" "Hello"     # 출력:      Hello

# 왼쪽 정렬
printf "%-10s\n" "Hello"    # 출력: Hello

# 0으로 패딩된 숫자
printf "%05d\n" 42          # 출력: 00042

# 부동소수점 정밀도
printf "%.3f\n" 3.14159     # 출력: 3.142

# 너비와 정밀도 함께
printf "%10.2f\n" 3.14159   # 출력:       3.14

2.3 포매팅된 테이블 만들기

#!/bin/bash

# 테이블 헤더 출력
printf "%-15s %-10s %10s\n" "Name" "Status" "Count"
printf "%-15s %-10s %10s\n" "===============" "==========" "=========="

# 데이터 행 출력
printf "%-15s %-10s %10d\n" "Alice" "Active" 42
printf "%-15s %-10s %10d\n" "Bob" "Inactive" 17
printf "%-15s %-10s %10d\n" "Charlie" "Active" 93

# 출력:
# Name            Status          Count
# =============== ==========     ==========
# Alice           Active             42
# Bob             Inactive           17
# Charlie         Active             93

2.4 변수로 printf

#!/bin/bash

# 포매팅된 문자열을 변수에 저장
printf -v timestamp "%(%Y-%m-%d %H:%M:%S)T" -1
echo "Current time: $timestamp"

# 복잡한 문자열 포맷
printf -v sql_query "SELECT * FROM %s WHERE id = %d" "users" 42
echo "$sql_query"

# CSV 줄 만들기
printf -v csv_line "%s,%d,%.2f" "Product A" 100 19.99
echo "$csv_line"

2.5 printf로 패턴 반복

#!/bin/bash

# 수평선 출력
printf '=%.0s' {1..50}
echo

# 포매팅된 구분자 출력
printf '%*s\n' 50 | tr ' ' '-'

# 진행 상황 막대 생성
create_progress_bar() {
    local percent=$1
    local width=50
    local filled=$((percent * width / 100))

    printf "["
    printf "%${filled}s" | tr ' ' '#'
    printf "%$((width - filled))s" | tr ' ' '-'
    printf "] %3d%%\n" "$percent"
}

create_progress_bar 75
# 출력: [#####################################---------------] 75%

2.6 실전 포매팅 예제

#!/bin/bash

# 통화 포맷
format_currency() {
    local amount=$1
    printf "$%'.2f\n" "$amount"
}

format_currency 1234567.89  # 출력: $1,234,567.89

# 파일 크기 포맷
format_size() {
    local size=$1
    local units=("B" "KB" "MB" "GB" "TB")
    local unit=0

    while ((size > 1024 && unit < 4)); do
        size=$((size / 1024))
        ((unit++))
    done

    printf "%.2f %s\n" "$size" "${units[$unit]}"
}

format_size 1048576  # 출력: 1.00 MB

# 기간 포맷 (초를 HH:MM:SS로)
format_duration() {
    local seconds=$1
    local hours=$((seconds / 3600))
    local minutes=$(( (seconds % 3600) / 60 ))
    local secs=$((seconds % 60))

    printf "%02d:%02d:%02d\n" "$hours" "$minutes" "$secs"
}

format_duration 3665  # 출력: 01:01:05

3. tr 명령

tr (translate) 명령은 문자 단위 변환을 수행합니다.

3.1 문자 변환

#!/bin/bash

# 문자 변환
echo "hello" | tr 'a-z' 'A-Z'  # 출력: HELLO
echo "WORLD" | tr 'A-Z' 'a-z'  # 출력: world

# 특정 문자 치환
echo "hello" | tr 'l' 'L'      # 출력: heLLo

# 다중 치환
echo "hello world" | tr 'elo' 'ELO'  # 출력: hELLO wOrLd

# 문자 회전 (ROT13)
echo "Hello World" | tr 'A-Za-z' 'N-ZA-Mn-za-m'  # 출력: Uryyb Jbeyq

3.2 문자 삭제

#!/bin/bash

# 특정 문자 삭제
echo "hello123world456" | tr -d '0-9'  # 출력: helloworld

# 공백 삭제
echo "  hello   world  " | tr -d ' '   # 출력: helloworld

# 줄바꿈 삭제 (줄 합치기)
cat multiline.txt | tr -d '\n'

# 모든 모음 제거
echo "Hello World" | tr -d 'aeiouAEIOU'  # 출력: Hll Wrld

# 구두점 제거
echo "Hello, World!" | tr -d '[:punct:]'  # 출력: Hello World

3.3 문자 압축

#!/bin/bash

# 반복된 문자 압축
echo "hello    world" | tr -s ' '     # 출력: hello world

# 여러 공백을 단일 공백으로 압축
echo "too    many     spaces" | tr -s '[:space:]' ' '

# 중복 빈 줄 제거
cat file.txt | tr -s '\n'

# 특정 문자 압축
echo "booook" | tr -s 'o'              # 출력: bok

3.4 보수 집합(Complement Set)

#!/bin/bash

# 영숫자만 유지 (나머지 모두 삭제)
echo "Hello, World! 123" | tr -cd '[:alnum:]'  # 출력: HelloWorld123

# 숫자만 유지
echo "Price: $19.99" | tr -cd '0-9'            # 출력: 1999

# 모든 비출력 문자 제거
cat file.txt | tr -cd '[:print:]\n'

3.5 문자 클래스

#!/bin/bash

# 사용 가능한 문자 클래스
# [:alnum:]  - 영숫자 문자
# [:alpha:]  - 알파벳 문자
# [:digit:]  - 숫자
# [:lower:]  - 소문자
# [:upper:]  - 대문자
# [:space:]  - 공백 문자
# [:punct:]  - 구두점 문자
# [:print:]  - 출력 가능 문자

# 예제
echo "Hello123" | tr '[:lower:]' '[:upper:]'   # 출력: HELLO123
echo "ABC def" | tr '[:upper:]' '[:lower:]'    # 출력: abc def
echo "Hello World" | tr -d '[:space:]'         # 출력: HelloWorld
echo "test@email.com" | tr -cd '[:alnum:]@.'   # 유효한 이메일 문자만 유지

3.6 실전 tr 예제

#!/bin/bash

# DOS/Windows 줄 끝을 Unix로 변환
tr -d '\r' < dos_file.txt > unix_file.txt

# 제목에서 URL slug 생성
echo "My Blog Post Title!" | tr '[:upper:] ' '[:lower:]-' | tr -cd '[:alnum:]-'
# 출력: my-blog-post-title

# 전화번호 숫자 추출
echo "Phone: (555) 123-4567" | tr -cd '0-9'    # 출력: 5551234567

# 텍스트에서 제어 문자 제거
cat file.txt | tr -d '[:cntrl:]'

# 파일명의 공백을 밑줄로 변환
filename="My Document.txt"
new_filename=$(echo "$filename" | tr ' ' '_')
echo "$new_filename"  # 출력: My_Document.txt

4. cut 명령

cut 명령은 각 줄에서 필드나 문자를 추출합니다.

4.1 문자 추출

#!/bin/bash

# 특정 문자 추출
echo "Hello World" | cut -c 1-5      # 출력: Hello
echo "Hello World" | cut -c 7-       # 출력: World
echo "Hello World" | cut -c -5       # 출력: Hello
echo "Hello World" | cut -c 1,7      # 출력: HW

# 여러 범위 추출
echo "abcdefghij" | cut -c 1-3,5-7   # 출력: abcefg

4.2 구분자로 필드 추출

#!/bin/bash

# CSV 파싱
echo "Alice,30,Engineer" | cut -d',' -f1     # 출력: Alice
echo "Alice,30,Engineer" | cut -d',' -f2     # 출력: 30
echo "Alice,30,Engineer" | cut -d',' -f1,3   # 출력: Alice,Engineer

# 탭 구분 (기본값)
echo -e "A\tB\tC\tD" | cut -f2               # 출력: B

# 파일에서 추출
cut -d':' -f1,3 /etc/passwd  # 사용자명과 UID 추출

# 여러 필드
echo "one:two:three:four:five" | cut -d':' -f2-4  # 출력: two:three:four

4.3 바이트 추출

#!/bin/bash

# 바이트 추출 (ASCII의 경우 문자와 유사)
echo "Hello" | cut -b 1-3   # 출력: Hel

# 바이너리 데이터나 멀티바이트 문자에 유용
# 참고: -b는 멀티바이트 UTF-8을 별도의 바이트로 처리

4.4 보수(Complement) (출력 억제)

#!/bin/bash

# 지정된 필드를 제외한 모든 필드 출력
echo "A,B,C,D,E" | cut -d',' -f1-3 --complement  # 출력: D,E

4.5 실전 cut 예제

#!/bin/bash

# 로그에서 IP 주소 추출
cut -d' ' -f1 access.log | sort -u

# /etc/passwd에서 사용자 목록 얻기
cut -d':' -f1 /etc/passwd

# 파일명에서 확장자 추출
echo "document.pdf" | rev | cut -d'.' -f1 | rev  # 출력: pdf

# 명령 출력 파싱
ps aux | tail -n +2 | cut -c 66-  # 명령 열 추출

# 타임스탬프에서 날짜 추출
echo "2024-02-13 15:30:45" | cut -d' ' -f1  # 출력: 2024-02-13

# 특정 열로 CSV 파싱
cut -d',' -f2,4,6 data.csv > extracted.csv

5. paste와 join

이 명령들은 여러 파일의 데이터를 병합합니다.

5.1 paste 명령

#!/bin/bash

# 파일을 나란히 병합
# file1.txt: A B C
# file2.txt: 1 2 3
paste file1.txt file2.txt
# 출력:
# A    1
# B    2
# C    3

# 사용자 정의 구분자
paste -d',' file1.txt file2.txt
# 출력:
# A,1
# B,2
# C,3

# 직렬 모드 (첫 번째 파일의 모든 줄, 그 다음 두 번째)
paste -s file1.txt file2.txt
# 출력:
# A    B    C
# 1    2    3

# 여러 파일 병합
paste file1.txt file2.txt file3.txt

# 여러 파일에서 CSV 생성
paste -d',' names.txt ages.txt cities.txt > output.csv

5.2 join 명령

#!/bin/bash

# 공통 필드로 파일 조인
# users.txt:    passwords.txt:
# 1 alice       1 pass123
# 2 bob         2 pass456
# 3 charlie     3 pass789

join users.txt passwords.txt
# 출력:
# 1 alice pass123
# 2 bob pass456
# 3 charlie pass789

# 사용자 정의 구분자
join -t',' users.csv passwords.csv

# 다른 필드로 조인
join -1 2 -2 1 file1.txt file2.txt  # file1 필드 2, file2 필드 1

# 외부 조인 (일치하지 않는 줄 포함)
join -a1 file1.txt file2.txt  # file1에서 일치하지 않는 것 포함
join -a2 file1.txt file2.txt  # file2에서 일치하지 않는 것 포함
join -a1 -a2 file1.txt file2.txt  # 완전 외부 조인

# 출력 형식 지정
join -o 1.1,1.2,2.2 file1.txt file2.txt

5.3 실전 예제

#!/bin/bash

# 이름과 성 결합
paste -d' ' first_names.txt last_names.txt > full_names.txt

# 열 데이터에서 테이블 생성
paste -d'|' col1.txt col2.txt col3.txt | column -t -s'|'

# 사용자 정보와 로그인 기록 조인
sort -k1 users.txt > users_sorted.txt
sort -k1 logins.txt > logins_sorted.txt
join -t',' users_sorted.txt logins_sorted.txt > user_logins.txt

# 데이터 전치 (행을 열로)
paste -s -d',' data.txt

# 번호가 매겨진 목록 생성
paste -d' ' <(seq 1 10) items.txt
# 출력:
# 1 item1
# 2 item2
# ...

6. column 명령

column 명령은 출력을 테이블로 포맷합니다.

6.1 기본 열 포매팅

#!/bin/bash

# 테이블로 자동 포맷
cat <<EOF | column -t
Name Age City
Alice 30 NYC
Bob 25 LA
Charlie 35 Chicago
EOF
# 출력:
# Name     Age  City
# Alice    30   NYC
# Bob      25   LA
# Charlie  35   Chicago

# 사용자 정의 구분자
echo -e "A,B,C\n1,2,3\n4,5,6" | column -t -s','
# 출력:
# A  B  C
# 1  2  3
# 4  5  6

6.2 행보다 열 먼저 채우기

#!/bin/bash

# 열 생성 (신문 스타일)
seq 1 20 | column -c 40
# 출력 (근사값):
# 1  5   9   13  17
# 2  6   10  14  18
# 3  7   11  15  19
# 4  8   12  16  20

6.3 JSON 포매팅

#!/bin/bash

# JSON을 테이블로 포맷 (-J 플래그가 있는 column 필요)
# 참고: GNU column은 -J 플래그가 있지만, BSD column은 없음

# 대안: jq로 데이터 준비 후 column 사용
jq -r '.[] | [.name, .age, .city] | @tsv' data.json | column -t

6.4 실전 예제

#!/bin/bash

# 명령 출력 포맷
ps aux | head -n 10 | column -t

# 정렬된 설정 파일 생성
cat > config.conf <<EOF
port=8080
host=localhost
debug=true
workers=4
EOF

cat config.conf | column -t -s'='
# 출력:
# port     8080
# host     localhost
# debug    true
# workers  4

# CSV 데이터를 깔끔하게 포맷
column -t -s',' data.csv

# 정렬된 메뉴 생성
cat <<MENU | column -t
1|Start|Launch the application
2|Stop|Terminate the application
3|Restart|Restart the application
4|Status|Check application status
MENU

7. jq로 JSON 처리

jq는 강력한 커맨드라인 JSON 프로세서입니다.

7.1 기본 필터

#!/bin/bash

# JSON 예쁘게 출력
echo '{"name":"Alice","age":30}' | jq '.'

# 필드 추출
echo '{"name":"Alice","age":30}' | jq '.name'  # 출력: "Alice"

# 중첩 필드 추출
echo '{"user":{"name":"Alice","age":30}}' | jq '.user.name'  # 출력: "Alice"

# 배열 요소
echo '["a","b","c"]' | jq '.[1]'  # 출력: "b"

# 배열 슬라이스
echo '[1,2,3,4,5]' | jq '.[2:4]'  # 출력: [3,4]

7.2 배열 연산

#!/bin/bash

# 배열 반복
echo '[1,2,3]' | jq '.[]'
# 출력:
# 1
# 2
# 3

# 배열에 맵
echo '[1,2,3]' | jq 'map(. * 2)'  # 출력: [2,4,6]

# 배열 필터
echo '[1,2,3,4,5]' | jq 'map(select(. > 2))'  # 출력: [3,4,5]

# 배열 길이
echo '[1,2,3,4,5]' | jq 'length'  # 출력: 5

# 배열 합계
echo '[1,2,3,4,5]' | jq 'add'  # 출력: 15

# 고유 값 얻기
echo '[1,2,2,3,3,3]' | jq 'unique'  # 출력: [1,2,3]

# 배열 정렬
echo '[3,1,2]' | jq 'sort'  # 출력: [1,2,3]

7.3 객체 연산

#!/bin/bash

# 키 얻기
echo '{"a":1,"b":2,"c":3}' | jq 'keys'  # 출력: ["a","b","c"]

# 값 얻기
echo '{"a":1,"b":2,"c":3}' | jq '.[]'
# 출력:
# 1
# 2
# 3

# 키 존재 확인
echo '{"a":1,"b":2}' | jq 'has("a")'  # 출력: true

# 필드 추가
echo '{"a":1}' | jq '. + {b: 2}'  # 출력: {"a":1,"b":2}

# 필드 삭제
echo '{"a":1,"b":2}' | jq 'del(.b)'  # 출력: {"a":1}

7.4 조건 논리

#!/bin/bash

# If-then-else
echo '{"age":25}' | jq 'if .age >= 18 then "adult" else "minor" end'

# 조건과 함께 select
echo '[{"name":"Alice","age":30},{"name":"Bob","age":17}]' | \
    jq '.[] | select(.age >= 18)'
# 출력: {"name":"Alice","age":30}

# 다중 조건
echo '[1,2,3,4,5]' | jq '.[] | select(. > 2 and . < 5)'
# 출력:
# 3
# 4

7.5 문자열 보간

#!/bin/bash

# 문자열 보간
echo '{"first":"Alice","last":"Smith"}' | \
    jq '"\(.first) \(.last)"'
# 출력: "Alice Smith"

# 객체 만들기
echo '{"name":"Alice"}' | \
    jq '{greeting: "Hello, \(.name)!"}'
# 출력: {"greeting":"Hello, Alice!"}

7.6 실전 jq 예제

#!/bin/bash

# API 응답 파싱
curl -s https://api.github.com/users/torvalds | jq '.name, .location, .public_repos'

# 객체 배열에서 특정 필드 추출
jq '.users[] | {name, email}' users.json

# JSON에서 CSV 생성
jq -r '.[] | [.name, .age, .city] | @csv' data.json

# 필터 및 변환
jq '.users | map(select(.active == true) | {name, email})' data.json

# 필드별 그룹화
jq 'group_by(.category)' items.json

# 중첩 구조 평탄화
jq '[.users[].orders[]] | length' data.json

# 필드 업데이트
jq '.users[] |= if .name == "Alice" then .status = "admin" else . end' data.json

# JSON 파일 병합
jq -s '.[0] * .[1]' file1.json file2.json

# 사용자 정의 들여쓰기로 예쁘게 출력
jq --indent 4 '.' data.json

# 원시 출력 (따옴표 없음)
jq -r '.name' data.json

# 압축 출력
jq -c '.' data.json

8. yq로 YAML 처리

yq는 jq와 유사한 YAML 프로세서입니다 (참고: yq라는 이름의 도구가 여러 개 있으며, 예제는 mikefarah/yq 사용).

8.1 기본 연산

#!/bin/bash

# 값 읽기
yq '.database.host' config.yaml

# 중첩 배열 읽기
yq '.servers[0].name' config.yaml

# 모든 배열 요소 읽기
yq '.servers[].name' config.yaml

# 키 얻기
yq 'keys' config.yaml

8.2 YAML 수정

#!/bin/bash

# 값 업데이트
yq '.database.port = 5432' config.yaml

# 새 필드 추가
yq '.newfield = "value"' config.yaml

# 필드 삭제
yq 'del(.oldfield)' config.yaml

# 제자리 업데이트
yq -i '.database.host = "localhost"' config.yaml

# YAML 파일 병합
yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' file1.yaml file2.yaml

8.3 형식 변환

#!/bin/bash

# YAML을 JSON으로
yq -o=json '.' config.yaml

# JSON을 YAML로
yq -P '.' data.json

# YAML 예쁘게 출력
yq '.' config.yaml

8.4 실전 예제

#!/bin/bash

# 데이터베이스 자격 증명 추출
DB_HOST=$(yq '.database.host' config.yaml)
DB_PORT=$(yq '.database.port' config.yaml)
DB_NAME=$(yq '.database.name' config.yaml)

# 설정 업데이트
yq -i ".app.version = \"$NEW_VERSION\"" config.yaml
yq -i ".app.updated_at = \"$(date -Iseconds)\"" config.yaml

# YAML 구문 검증
if yq '.' config.yaml > /dev/null 2>&1; then
    echo "Valid YAML"
else
    echo "Invalid YAML"
fi

# 모든 서비스 포트 추출
yq '.services[].port' docker-compose.yml

# YAML에서 환경 파일 만들기
yq -o=props '.env' config.yaml > .env

9. 실전 텍스트 처리 파이프라인

9.1 로그 분석

#!/bin/bash

# 타임스탬프와 함께 에러 메시지 추출
grep ERROR app.log | cut -d' ' -f1-2,4- | sort | uniq -c

# 유형별 에러 카운트
grep ERROR app.log | cut -d':' -f3 | sort | uniq -c | sort -rn

# 접근 로그의 상위 10개 IP 주소
cut -d' ' -f1 access.log | sort | uniq -c | sort -rn | head -10

# 로그 항목 파싱 및 재포맷
awk -F'[\\[\\]]' '{print $1, $2, $3}' access.log | \
    column -t > formatted.log

9.2 데이터 변환

#!/bin/bash

# CSV를 JSON으로 변환
csv_to_json() {
    local csv_file=$1

    # 헤더 읽기
    IFS=',' read -ra headers < "$csv_file"

    # 데이터 행 처리
    tail -n +2 "$csv_file" | while IFS=',' read -ra values; do
        echo "{"
        for i in "${!headers[@]}"; do
            printf '  "%s": "%s"' "${headers[$i]}" "${values[$i]}"
            [[ $i -lt $((${#headers[@]} - 1)) ]] && echo ","
        done
        echo "\n}"
    done | jq -s '.'
}

# 필드명 변환
jq 'map({username: .user, email_address: .email, full_name: "\(.first) \(.last)"})' \
    input.json > output.json

# 데이터 피벗 (행을 열로)
paste -s -d',' data.txt

9.3 보고서 생성

#!/bin/bash

# 시스템 보고서 생성
generate_report() {
    cat <<EOF | column -t -s'|'
Metric|Value|Unit
---|---|---
CPU Usage|$(top -bn1 | grep "Cpu(s)" | awk '{print $2}')%|
Memory Used|$(free -m | awk 'NR==2{print $3}')|MB
Disk Usage|$(df -h / | awk 'NR==2{print $5}')|
Uptime|$(uptime -p)|
Load Average|$(uptime | awk -F'load average:' '{print $2}')|
EOF
}

# CSV에서 마크다운 테이블 생성
csv_to_markdown() {
    local csv=$1

    # 헤더
    head -1 "$csv" | tr ',' '|' | sed 's/^/|/' | sed 's/$/|/'

    # 구분자
    head -1 "$csv" | tr ',' '|' | sed 's/[^|]/-/g' | sed 's/^/|/' | sed 's/$/|/'

    # 데이터
    tail -n +2 "$csv" | tr ',' '|' | sed 's/^/|/' | sed 's/$/|/'
}

연습 문제

문제 1: 고급 CSV 프로세서

다음 기능을 가진 스크립트 생성: - 헤더 행이 있는 CSV 파일 읽기 - 각 행 검증 (올바른 필드 수, 데이터 타입) - 열 값에 기반한 행 필터링 지원 (예: age > 30) - 다중 열로 정렬 지원 - 특정 열 선택 지원 - CSV, JSON 또는 포맷된 테이블로 출력 - 쉼표가 있는 따옴표로 묶인 필드를 올바르게 처리

문제 2: 로그 파서 및 분석기

다음 기능을 가진 로그 분석 도구 구축: - 일반 로그 형식 파싱 (Apache, Nginx, syslog) - 타임스탬프, 레벨, 메시지, 소스 추출 - 통계 생성 (에러율, 상위 에러, 시간 분포) - ASCII 문자를 사용한 타임라인 시각화 생성 - 시간 범위, 레벨, 패턴으로 필터링 지원 - 마크다운 또는 HTML 형식으로 보고서 출력

문제 3: 설정 파일 변환기

다음 기능을 가진 도구 작성: - JSON, YAML, TOML, INI, ENV 형식 간 변환 - 각 형식의 구문 검증 - 가능한 경우 주석 보존 - 중첩 구조 지원 - 배열 및 복잡한 타입 처리 - 커맨드라인을 통해 특정 값 추출/업데이트 가능 - 여러 설정 파일 병합 지원

문제 4: 텍스트 템플릿 엔진

다음 기능을 가진 템플릿 프로세서 구현: - 플레이스홀더가 있는 템플릿 파일 읽기 (예: {{variable}}) - 조건문 지원: {{#if condition}}...{{/if}} - 루프 지원: {{#each items}}...{{/each}} - 인클루드 지원: {{> include file.txt}} - JSON/YAML 파일 또는 환경에서 변수 읽기 - 필터 지원: {{variable|upper}}, {{variable|date}} - 중첩 데이터 구조 처리

문제 5: 데이터 검증 프레임워크

다음 기능을 가진 검증 도구 생성: - YAML 형식으로 검증 규칙 정의 - 규칙에 대해 CSV/JSON/YAML 데이터 검증 - 규칙 지원: required, type, range, pattern, length, custom - 줄 번호와 필드명과 함께 검증 에러 보고 - 교차 필드 검증 지원 (예: end_date > start_date) - 여러 형식으로 검증 보고서 생성 - 일반 문제 자동 수정 가능 (공백 제거, 대소문자 변환)

이전: 06_IO_and_Redirection.md | 다음: 08_Regex_in_Bash.md

to navigate between lessons