Bash 로 파일의 줄을 반복하는 방법

ShellBeginner
지금 연습하기

소개

이 실습 튜토리얼에서는 Bash 스크립트를 사용하여 파일을 줄 단위로 처리하는 방법을 배우게 됩니다. 텍스트 파일 처리는 Linux 시스템 관리 및 자동화에서 가장 일반적인 작업 중 하나이며, 파일의 각 줄을 반복하는 방법을 이해하는 것은 구성 파일, 로그 및 데이터 처리를 다루는 데 필수적인 기술입니다.

이 튜토리얼을 마치면 다음을 수행할 수 있습니다.

  • 파일을 읽고 처리하는 기본적인 Bash 스크립트 생성
  • 파일의 줄을 반복하는 다양한 기술 사용
  • 빈 줄 및 특수 문자와 같은 특수한 경우 처리
  • 이러한 기술을 실제 예제에 적용

Linux 를 처음 접하거나 스크립팅 기술을 향상시키려는 경우, 이 튜토리얼은 Bash 에서 텍스트 파일을 효율적으로 처리하는 데 필요한 지식을 제공합니다.

이것은 가이드 실험입니다. 학습과 실습을 돕기 위한 단계별 지침을 제공합니다.각 단계를 완료하고 실무 경험을 쌓기 위해 지침을 주의 깊게 따르세요. 과거 데이터에 따르면, 이것은 초급 레벨의 실험이며 완료율은 81%입니다.학습자들로부터 93%의 긍정적인 리뷰율을 받았습니다.

예제 파일 생성 및 기본 Bash 스크립트

파일 처리 기술을 살펴보기 전에, 먼저 작업할 몇 가지 예제 파일을 만들고 Bash 스크립팅의 기본 사항을 배우겠습니다.

샘플 텍스트 파일 생성

LabEx 환경에서 터미널을 엽니다. /home/labex/project 디렉토리에 있어야 합니다. 작업할 간단한 텍스트 파일을 만들어 보겠습니다.

  1. 연습을 위한 디렉토리를 생성합니다.
mkdir -p ~/project/file_processing
cd ~/project/file_processing
  1. 다음 명령을 사용하여 샘플 텍스트 파일을 생성합니다.
cat > sample.txt << EOF
This is the first line of the file.
This is the second line.
This is the third line.

This line comes after an empty line.
This is the last line of the file.
EOF

이 명령은 빈 줄을 포함하여 6 줄로 구성된 sample.txt라는 파일을 생성합니다.

기본 Bash 스크립트 이해

Bash 스크립트는 Bash 셸에 의해 실행되는 일련의 명령을 포함하는 텍스트 파일입니다. 다음은 Bash 스크립트의 주요 구성 요소입니다.

  1. Shebang Line (Shebang 라인): Bash 스크립트의 첫 번째 줄은 일반적으로 #!/bin/bash로 시작하여 스크립트가 Bash 인터프리터에 의해 실행되어야 함을 나타냅니다.

  2. Comments (주석): #로 시작하는 줄은 주석이며 셸에서 무시됩니다.

  3. Commands (명령): 스크립트는 순차적으로 실행되는 셸 명령으로 구성됩니다.

  4. Variables (변수): 변수를 사용하여 데이터를 저장하고 조작할 수 있습니다.

샘플 파일의 내용을 표시하는 간단한 Bash 스크립트를 만들어 보겠습니다.

cat > display_file.sh << EOF
#!/bin/bash

## A simple script to display the contents of a file
echo "Displaying the contents of sample.txt:"
echo "---------------------------------"
cat sample.txt
echo "---------------------------------"
echo "File displayed successfully!"
EOF

이제 스크립트를 실행 가능하게 만들고 실행합니다.

chmod +x display_file.sh
./display_file.sh

다음 출력을 볼 수 있습니다.

Displaying the contents of sample.txt:
---------------------------------
This is the first line of the file.
This is the second line.
This is the third line.

This line comes after an empty line.
This is the last line of the file.
---------------------------------
File displayed successfully!

축하합니다! 첫 번째 Bash 스크립트를 만들었습니다. 이제 파일을 줄 단위로 처리하는 방법을 배우겠습니다.

While 루프를 사용하여 파일 줄 읽기

Bash 에서 파일을 줄 단위로 읽는 가장 일반적이고 강력한 방법은 while 루프와 read 명령을 함께 사용하는 것입니다. 이 접근 방식은 다른 방법보다 공백, 빈 줄 및 특수 문자를 더 잘 처리합니다.

기본 While 루프 구조

while 루프를 사용하여 sample.txt를 줄 단위로 읽는 스크립트를 만들어 보겠습니다.

  1. 아직 해당 디렉토리에 있지 않다면 작업 디렉토리로 이동합니다.
cd ~/project/file_processing
  1. 새 스크립트 파일을 생성합니다.
cat > read_lines_while.sh << EOF
#!/bin/bash

## Script to read a file line by line using a while loop
file_path="sample.txt"

echo "Reading file: \$file_path using while loop"
echo "---------------------------------"

## Using while loop to read the file line by line
line_number=1
while read -r line; do
    echo "Line \$line_number: \$line"
    line_number=\$((line_number + 1))
done < "\$file_path"

echo "---------------------------------"
echo "File reading completed!"
EOF
  1. 스크립트를 실행 가능하게 만들고 실행합니다.
chmod +x read_lines_while.sh
./read_lines_while.sh

다음과 유사한 출력을 볼 수 있습니다.

Reading file: sample.txt using while loop
---------------------------------
Line 1: This is the first line of the file.
Line 2: This is the second line.
Line 3: This is the third line.
Line 4:
Line 5: This line comes after an empty line.
Line 6: This is the last line of the file.
---------------------------------
File reading completed!

While 루프 접근 방식 이해

이 접근 방식의 주요 구성 요소를 분석해 보겠습니다.

  1. while read -r line; do: 이 명령은 입력에서 한 번에 한 줄씩 읽어 line이라는 변수에 저장하는 while 루프를 시작합니다.

  2. read-r 옵션은 이스케이프 문자로 해석하는 대신 입력에서 백슬래시를 보존합니다. 이는 백슬래시를 포함할 수 있는 파일 내용을 처리할 때 중요합니다.

  3. done < "$file_path": 이는 $file_path로 지정된 파일의 내용을 while 루프의 입력으로 리디렉션합니다.

  4. 루프 내부에서 각 줄을 필요에 따라 처리할 수 있습니다. 이 경우 단순히 줄 번호와 함께 출력합니다.

While 루프 접근 방식의 장점

while read 접근 방식에는 몇 가지 장점이 있습니다.

  1. 각 줄의 공백을 보존합니다.
  2. 빈 줄을 올바르게 처리합니다.
  3. 파일을 줄 단위로 처리하므로 큰 파일에 대해 메모리 효율적입니다.
  4. 파일의 특수 문자를 처리할 수 있습니다.

다른 파일에 맞게 스크립트 수정

인수를 파일 경로로 허용하도록 스크립트를 수정해 보겠습니다.

cat > read_lines_while_arg.sh << EOF
#!/bin/bash

## Script to read a file line by line using a while loop
## Usage: ./read_lines_while_arg.sh <file_path>

if [ \$## -eq 0 ]; then
    echo "Error: No file specified"
    echo "Usage: \$0 <file_path>"
    exit 1
fi

file_path="\$1"

if [ ! -f "\$file_path" ]; then
    echo "Error: File '\$file_path' does not exist"
    exit 1
fi

echo "Reading file: \$file_path using while loop"
echo "---------------------------------"

## Using while loop to read the file line by line
line_number=1
while read -r line; do
    echo "Line \$line_number: \$line"
    line_number=\$((line_number + 1))
done < "\$file_path"

echo "---------------------------------"
echo "File reading completed!"
EOF

스크립트를 실행 가능하게 만들고 다른 파일로 시도해 봅니다.

chmod +x read_lines_while_arg.sh
./read_lines_while_arg.sh sample.txt

이제 이 스크립트를 사용하여 모든 텍스트 파일을 줄 단위로 읽을 수 있습니다. 테스트할 다른 샘플 파일을 만들어 보겠습니다.

cat > numbers.txt << EOF
1
2
3
4
5
EOF

./read_lines_while_arg.sh numbers.txt

다음과 같은 출력을 볼 수 있습니다.

Reading file: numbers.txt using while loop
---------------------------------
Line 1: 1
Line 2: 2
Line 3: 3
Line 4: 4
Line 5: 5
---------------------------------
File reading completed!

이 접근 방식은 매우 다재다능하며 이후 단계에서 더 복잡한 파일 처리 작업의 기반이 될 것입니다.

For 루프를 사용하여 파일 줄 읽기

while 루프 방식이 일반적으로 파일을 줄 단위로 읽는 데 선호되지만, Bash 는 for 루프 방식도 제공합니다. 이 방법은 특정 시나리오에서 유용할 수 있으며 이해할 가치가 있습니다.

기본 For 루프 구조

for 루프를 사용하여 sample.txt를 줄 단위로 읽는 스크립트를 만들어 보겠습니다.

  1. 아직 해당 디렉토리에 있지 않다면 작업 디렉토리로 이동합니다.
cd ~/project/file_processing
  1. 새 스크립트 파일을 생성합니다.
cat > read_lines_for.sh << EOF
#!/bin/bash

## Script to read a file line by line using a for loop
file_path="sample.txt"

echo "Reading file: \$file_path using for loop"
echo "---------------------------------"

## Using for loop with the cat command
line_number=1
for line in \$(cat "\$file_path"); do
    echo "Line \$line_number: \$line"
    line_number=\$((line_number + 1))
done

echo "---------------------------------"
echo "File reading completed!"
EOF
  1. 스크립트를 실행 가능하게 만들고 실행합니다.
chmod +x read_lines_for.sh
./read_lines_for.sh

출력에서 흥미로운 점을 발견할 수 있습니다.

Reading file: sample.txt using for loop
---------------------------------
Line 1: This
Line 2: is
Line 3: the
Line 4: first
Line 5: line
Line 6: of
Line 7: the
Line 8: file.
Line 9: This
...
---------------------------------
File reading completed!

For 루프의 제한 사항 이해

출력이 예상과 다를 수 있습니다. 줄 단위로 처리하는 대신 for 루프는 공백으로 파일을 분할했습니다. 이는 Bash 에서 for 루프의 기본 동작이 공백, 탭 및 줄 바꿈으로 입력을 분할하기 때문입니다.

이러한 제한 사항을 해결하기 위해 줄 구조를 유지하는 for 루프를 사용하는 다른 접근 방식을 사용할 수 있습니다.

cat > read_lines_for_improved.sh << EOF
#!/bin/bash

## Improved script to read a file line by line using a for loop
file_path="sample.txt"

echo "Reading file: \$file_path using improved for loop"
echo "---------------------------------"

## Save the current IFS (Internal Field Separator)
old_IFS="\$IFS"
## Set IFS to newline only
IFS=\$'\n'

## Using for loop with the cat command and modified IFS
line_number=1
for line in \$(cat "\$file_path"); do
    echo "Line \$line_number: \$line"
    line_number=\$((line_number + 1))
done

## Restore the original IFS
IFS="\$old_IFS"

echo "---------------------------------"
echo "File reading completed!"
EOF

스크립트를 실행 가능하게 만들고 실행합니다.

chmod +x read_lines_for_improved.sh
./read_lines_for_improved.sh

이제 출력은 다음과 유사해야 합니다.

Reading file: sample.txt using improved for loop
---------------------------------
Line 1: This is the first line of the file.
Line 2: This is the second line.
Line 3: This is the third line.
Line 4:
Line 5: This line comes after an empty line.
Line 6: This is the last line of the file.
---------------------------------
File reading completed!

While 루프와 For 루프 방식 비교

두 방법의 차이점을 더 잘 설명하기 위해 더 복잡한 파일을 만들어 보겠습니다.

cat > complex.txt << EOF
Line with spaces:   multiple   spaces   here
Line with "double quotes" and 'single quotes'
Line with special characters: !@#\$%^&*()
Line with a backslash: C:\\Program Files\\App
EOF

이제 두 가지 방법을 비교하는 스크립트를 만들어 보겠습니다.

cat > compare_methods.sh << EOF
#!/bin/bash

## Script to compare while loop and for loop methods
file_path="complex.txt"

echo "WHILE LOOP METHOD:"
echo "---------------------------------"
line_number=1
while read -r line; do
    echo "Line \$line_number: \$line"
    line_number=\$((line_number + 1))
done < "\$file_path"
echo "---------------------------------"

echo "FOR LOOP METHOD (with modified IFS):"
echo "---------------------------------"
## Save the current IFS
old_IFS="\$IFS"
## Set IFS to newline only
IFS=\$'\n'

line_number=1
for line in \$(cat "\$file_path"); do
    echo "Line \$line_number: \$line"
    line_number=\$((line_number + 1))
done

## Restore the original IFS
IFS="\$old_IFS"
echo "---------------------------------"
EOF

스크립트를 실행 가능하게 만들고 실행합니다.

chmod +x compare_methods.sh
./compare_methods.sh

각 방법이 복잡한 파일을 어떻게 처리하는지 확인하기 위해 출력을 검토합니다. while 루프 방식은 개선된 IFS 처리 방식에서도 for 루프보다 일반적으로 특수한 경우를 더 잘 처리한다는 것을 알 수 있습니다.

결론

탐구를 바탕으로 다음과 같이 결론을 내릴 수 있습니다.

  1. while read 방식은 일반적으로 더 강력하며 특수한 경우를 더 잘 처리합니다.
  2. for 루프 방식은 간단한 경우에 유용할 수 있지만 IFS 변수를 신중하게 처리해야 합니다.
  3. 파일을 줄 단위로 처리할 때는 안정성을 위해 일반적으로 while read 방식을 선호합니다.

다음 단계에서는 파일을 처리할 때 빈 줄 및 기타 예외 사례를 처리하는 방법을 살펴보겠습니다.

특수 사례 및 예외 조건 처리

Bash 에서 파일을 처리할 때 빈 줄, 특수 문자가 있는 줄 또는 특이한 형식의 파일과 같은 특수한 경우를 자주 접하게 됩니다. 이 단계에서는 이러한 예외 조건을 효과적으로 처리하는 방법을 살펴보겠습니다.

빈 줄 처리

파일을 처리할 때 빈 줄을 처리하는 방법을 보여주는 스크립트를 만들어 보겠습니다.

  1. 작업 디렉토리로 이동합니다.
cd ~/project/file_processing
  1. 빈 줄이 있는 파일을 만듭니다.
cat > empty_lines.txt << EOF
This is line 1
This is line 2

This is line 4 (after an empty line)

This is line 6 (after another empty line)
EOF
  1. 빈 줄을 처리하는 스크립트를 만듭니다.
cat > handle_empty_lines.sh << EOF
#!/bin/bash

## Script to demonstrate handling empty lines
file_path="empty_lines.txt"

echo "Reading file and showing all lines (including empty ones):"
echo "---------------------------------"
line_number=1
while read -r line; do
    echo "Line \$line_number: [\$line]"
    line_number=\$((line_number + 1))
done < "\$file_path"
echo "---------------------------------"

echo "Reading file and skipping empty lines:"
echo "---------------------------------"
line_number=1
while read -r line; do
    ## Check if the line is empty
    if [ -n "\$line" ]; then
        echo "Line \$line_number: \$line"
        line_number=\$((line_number + 1))
    fi
done < "\$file_path"
echo "---------------------------------"
EOF
  1. 스크립트를 실행 가능하게 만들고 실행합니다.
chmod +x handle_empty_lines.sh
./handle_empty_lines.sh

다음과 유사한 출력을 볼 수 있습니다.

Reading file and showing all lines (including empty ones):
---------------------------------
Line 1: [This is line 1]
Line 2: [This is line 2]
Line 3: []
Line 4: [This is line 4 (after an empty line)]
Line 5: []
Line 6: [This is line 6 (after another empty line)]
---------------------------------
Reading file and skipping empty lines:
---------------------------------
Line 1: This is line 1
Line 2: This is line 2
Line 3: This is line 4 (after an empty line)
Line 4: This is line 6 (after another empty line)
---------------------------------

구분된 파일 (CSV) 작업

많은 데이터 파일은 필드를 구분하기 위해 쉼표 (CSV) 또는 탭 (TSV) 과 같은 구분 기호를 사용합니다. 간단한 CSV 파일을 처리하는 스크립트를 만들어 보겠습니다.

  1. 샘플 CSV 파일을 만듭니다.
cat > users.csv << EOF
id,name,email,age
1,John Doe,john@example.com,32
2,Jane Smith,jane@example.com,28
3,Bob Johnson,bob@example.com,45
4,Alice Brown,alice@example.com,37
EOF
  1. 이 CSV 파일을 처리하는 스크립트를 만듭니다.
cat > process_csv.sh << EOF
#!/bin/bash

## Script to process a CSV file
file_path="users.csv"

echo "Processing CSV file: \$file_path"
echo "---------------------------------"

## Skip the header line and process each data row
line_number=0
while IFS=, read -r id name email age; do
    ## Skip the header line
    if [ \$line_number -eq 0 ]; then
        echo "Headers: ID, Name, Email, Age"
        line_number=\$((line_number + 1))
        continue
    fi
    
    echo "User \$id: \$name (Age: \$age) - Email: \$email"
    line_number=\$((line_number + 1))
done < "\$file_path"

echo "---------------------------------"
echo "Total records processed: \$((\$line_number - 1))"
EOF
  1. 스크립트를 실행 가능하게 만들고 실행합니다.
chmod +x process_csv.sh
./process_csv.sh

다음과 유사한 출력을 볼 수 있습니다.

Processing CSV file: users.csv
---------------------------------
Headers: ID, Name, Email, Age
User 1: John Doe (Age: 32) - Email: john@example.com
User 2: Jane Smith (Age: 28) - Email: jane@example.com
User 3: Bob Johnson (Age: 45) - Email: bob@example.com
User 4: Alice Brown (Age: 37) - Email: alice@example.com
---------------------------------
Total records processed: 4

특수 문자가 있는 파일 처리

문제를 일으킬 수 있는 특수 문자가 포함된 파일을 처리해 보겠습니다.

  1. 특수 문자가 있는 파일을 만듭니다.
cat > special_chars.txt << EOF
Line with asterisks: *****
Line with dollar signs: \$\$\$\$\$
Line with backslashes: \\\\\\
Line with quotes: "quoted text" and 'single quotes'
Line with backticks: \`command\`
EOF
  1. 특수 문자를 처리하는 스크립트를 만듭니다.
cat > handle_special_chars.sh << EOF
#!/bin/bash

## Script to demonstrate handling special characters
file_path="special_chars.txt"

echo "Reading file with special characters:"
echo "---------------------------------"
while read -r line; do
    ## Using printf instead of echo for better handling of special characters
    printf "Line: %s\\n" "\$line"
done < "\$file_path"
echo "---------------------------------"

echo "Escaping special characters for shell processing:"
echo "---------------------------------"
while read -r line; do
    ## Escape characters that have special meaning in shell
    escaped_line=\$(echo "\$line" | sed 's/[\$\`"'\''\\\\*]/\\\\&/g')
    echo "Original: \$line"
    echo "Escaped:  \$escaped_line"
    echo ""
done < "\$file_path"
echo "---------------------------------"
EOF
  1. 스크립트를 실행 가능하게 만들고 실행합니다.
chmod +x handle_special_chars.sh
./handle_special_chars.sh

스크립트가 특수 문자를 처리하는 방식을 확인하기 위해 출력을 검토합니다.

매우 큰 파일 처리

매우 큰 파일을 처리할 때는 메모리 효율적인 기술을 사용하는 것이 중요합니다. 전체 파일을 메모리에 로드하지 않고 큰 파일을 줄 단위로 처리하는 방법을 보여주는 스크립트를 만들어 보겠습니다.

cat > process_large_file.sh << EOF
#!/bin/bash

## Script to demonstrate processing a large file efficiently
## For demonstration, we'll create a simulated large file

echo "Creating a simulated large file..."
## Create a file with 1000 lines for demonstration
for i in {1..1000}; do
    echo "This is line number \$i in the simulated large file" >> large_file.txt
done

echo "Processing large file line by line (showing only first 5 lines):"
echo "---------------------------------"
count=0
while read -r line; do
    ## Process only first 5 lines for demonstration
    if [ \$count -lt 5 ]; then
        echo "Line \$((count + 1)): \$line"
    elif [ \$count -eq 5 ]; then
        echo "... (remaining lines not shown) ..."
    fi
    count=\$((count + 1))
done < "large_file.txt"
echo "---------------------------------"
echo "Total lines processed: \$count"

## Clean up
echo "Cleaning up temporary file..."
rm large_file.txt
EOF

스크립트를 실행 가능하게 만들고 실행합니다.

chmod +x process_large_file.sh
./process_large_file.sh

출력은 시연 목적으로 데이터의 하위 집합만 표시하면서 큰 파일을 줄 단위로 효율적으로 처리하는 방법을 보여줍니다.

결론

이 단계에서는 Bash 에서 파일을 처리할 때 다양한 특수한 경우와 예외 조건을 처리하는 방법을 배웠습니다.

  1. 빈 줄은 조건부 검사를 사용하여 처리할 수 있습니다.
  2. 구분된 파일 (예: CSV) 은 IFS 변수를 설정하여 처리할 수 있습니다.
  3. 특수 문자는 printf 또는 문자 이스케이프와 같은 기술을 사용하여 신중하게 처리해야 합니다.
  4. 큰 파일은 전체 파일을 메모리에 로드하지 않고 줄 단위로 효율적으로 처리할 수 있습니다.

이러한 기술은 Bash 에서 더 강력하고 다재다능한 파일 처리 스크립트를 만드는 데 도움이 됩니다.

실용적인 로그 분석 스크립트 만들기

이제 Bash 에서 파일을 줄 단위로 처리하는 다양한 기술을 배웠으므로, 이 지식을 적용하여 실용적인 로그 분석 스크립트를 만들어 보겠습니다. 이 스크립트는 샘플 웹 서버 로그 파일을 분석하여 유용한 정보를 추출하고 요약합니다.

샘플 로그 파일 만들기

먼저 샘플 웹 서버 액세스 로그 파일을 만들어 보겠습니다.

  1. 작업 디렉토리로 이동합니다.
cd ~/project/file_processing
  1. 샘플 액세스 로그 파일을 만듭니다.
cat > access.log << EOF
192.168.1.100 - - [10/Oct/2023:13:55:36 -0700] "GET /index.html HTTP/1.1" 200 2326
192.168.1.101 - - [10/Oct/2023:13:56:12 -0700] "GET /about.html HTTP/1.1" 200 1821
192.168.1.102 - - [10/Oct/2023:13:57:34 -0700] "GET /images/logo.png HTTP/1.1" 200 4562
192.168.1.100 - - [10/Oct/2023:13:58:45 -0700] "GET /css/style.css HTTP/1.1" 200 1024
192.168.1.103 - - [10/Oct/2023:13:59:01 -0700] "GET /login.php HTTP/1.1" 302 0
192.168.1.103 - - [10/Oct/2023:13:59:02 -0700] "GET /dashboard.php HTTP/1.1" 200 3652
192.168.1.104 - - [10/Oct/2023:14:00:15 -0700] "POST /login.php HTTP/1.1" 401 285
192.168.1.105 - - [10/Oct/2023:14:01:25 -0700] "GET /nonexistent.html HTTP/1.1" 404 876
192.168.1.102 - - [10/Oct/2023:14:02:45 -0700] "GET /contact.html HTTP/1.1" 200 1762
192.168.1.106 - - [10/Oct/2023:14:03:12 -0700] "GET /images/banner.jpg HTTP/1.1" 200 8562
192.168.1.100 - - [10/Oct/2023:14:04:33 -0700] "GET /products.html HTTP/1.1" 200 4521
192.168.1.107 - - [10/Oct/2023:14:05:16 -0700] "POST /subscribe.php HTTP/1.1" 500 652
192.168.1.108 - - [10/Oct/2023:14:06:27 -0700] "GET /api/data.json HTTP/1.1" 200 1824
192.168.1.103 - - [10/Oct/2023:14:07:44 -0700] "GET /logout.php HTTP/1.1" 302 0
192.168.1.109 - - [10/Oct/2023:14:08:55 -0700] "GET / HTTP/1.1" 200 2326
EOF

기본 로그 분석 스크립트 만들기

이 로그 파일을 분석하고 유용한 정보를 추출하는 스크립트를 만들어 보겠습니다.

cat > analyze_log.sh << EOF
#!/bin/bash

## Script to analyze a web server access log file
log_file="access.log"

echo "Analyzing log file: \$log_file"
echo "======================================"

## Count total number of entries
total_entries=\$(wc -l < "\$log_file")
echo "Total log entries: \$total_entries"
echo "--------------------------------------"

## Count unique IP addresses
echo "Unique IP addresses:"
echo "--------------------------------------"
unique_ips=0
declare -A ip_count

while read -r line; do
    ## Extract IP address (first field in each line)
    ip=\$(echo "\$line" | awk '{print \$1}')
    
    ## Count occurrences of each IP
    if [ -n "\$ip" ]; then
        if [ -z "\${ip_count[\$ip]}" ]; then
            ip_count[\$ip]=1
            unique_ips=\$((unique_ips + 1))
        else
            ip_count[\$ip]=\$((ip_count[\$ip] + 1))
        fi
    fi
done < "\$log_file"

## Display the IP addresses and their counts
for ip in "\${!ip_count[@]}"; do
    echo "\$ip: \${ip_count[\$ip]} requests"
done

echo "--------------------------------------"
echo "Total unique IP addresses: \$unique_ips"
echo "--------------------------------------"

## Count HTTP status codes
echo "HTTP Status Code Distribution:"
echo "--------------------------------------"
declare -A status_codes

while read -r line; do
    ## Extract status code (9th field in typical Apache log format)
    status=\$(echo "\$line" | awk '{print \$9}')
    
    ## Count occurrences of each status code
    if [ -n "\$status" ]; then
        if [ -z "\${status_codes[\$status]}" ]; then
            status_codes[\$status]=1
        else
            status_codes[\$status]=\$((status_codes[\$status] + 1))
        fi
    fi
done < "\$log_file"

## Display the status codes and their counts
for status in "\${!status_codes[@]}"; do
    case "\$status" in
        200) description="OK" ;;
        302) description="Found/Redirect" ;;
        401) description="Unauthorized" ;;
        404) description="Not Found" ;;
        500) description="Internal Server Error" ;;
        *) description="Other" ;;
    esac
    echo "Status \$status (\$description): \${status_codes[\$status]} requests"
done

echo "--------------------------------------"

## Identify requested resources
echo "Top requested resources:"
echo "--------------------------------------"
declare -A resources

while read -r line; do
    ## Extract the requested URL (typical format: "GET /path HTTP/1.1")
    request=\$(echo "\$line" | awk -F'"' '{print \$2}')
    method=\$(echo "\$request" | awk '{print \$1}')
    resource=\$(echo "\$request" | awk '{print \$2}')
    
    ## Count occurrences of each resource
    if [ -n "\$resource" ]; then
        if [ -z "\${resources[\$resource]}" ]; then
            resources[\$resource]=1
        else
            resources[\$resource]=\$((resources[\$resource] + 1))
        fi
    fi
done < "\$log_file"

## Display the top resources
## For simplicity, we'll just show all resources
for resource in "\${!resources[@]}"; do
    echo "\$resource: \${resources[\$resource]} requests"
done

echo "======================================"
echo "Analysis complete!"
EOF
  1. 스크립트를 실행 가능하게 만들고 실행합니다.
chmod +x analyze_log.sh
./analyze_log.sh

출력은 다음을 포함하여 액세스 로그에 대한 자세한 분석을 제공합니다.

  • 총 로그 항목 수
  • 고유 IP 주소 및 해당 요청 수
  • HTTP 상태 코드 분포
  • 가장 많이 요청된 리소스

로그 분석 스크립트 향상

추가적인 유용한 분석을 포함하도록 스크립트를 향상시켜 보겠습니다.

cat > enhanced_log_analyzer.sh << EOF
#!/bin/bash

## Enhanced script to analyze a web server access log file
log_file="access.log"

echo "Enhanced Log File Analysis: \$log_file"
echo "======================================"

## Count total number of entries
total_entries=\$(wc -l < "\$log_file")
echo "Total log entries: \$total_entries"
echo "--------------------------------------"

## Count unique IP addresses
echo "Unique IP addresses:"
echo "--------------------------------------"
unique_ips=0
declare -A ip_count

while read -r line; do
    ## Extract IP address (first field in each line)
    ip=\$(echo "\$line" | awk '{print \$1}')
    
    ## Count occurrences of each IP
    if [ -n "\$ip" ]; then
        if [ -z "\${ip_count[\$ip]}" ]; then
            ip_count[\$ip]=1
            unique_ips=\$((unique_ips + 1))
        else
            ip_count[\$ip]=\$((ip_count[\$ip] + 1))
        fi
    fi
done < "\$log_file"

## Display the IP addresses and their counts
for ip in "\${!ip_count[@]}"; do
    echo "\$ip: \${ip_count[\$ip]} requests"
done

echo "--------------------------------------"
echo "Total unique IP addresses: \$unique_ips"
echo "--------------------------------------"

## Count HTTP status codes
echo "HTTP Status Code Distribution:"
echo "--------------------------------------"
declare -A status_codes

while read -r line; do
    ## Extract status code (9th field in typical Apache log format)
    status=\$(echo "\$line" | awk '{print \$9}')
    
    ## Count occurrences of each status code
    if [ -n "\$status" ]; then
        if [ -z "\${status_codes[\$status]}" ]; then
            status_codes[\$status]=1
        else
            status_codes[\$status]=\$((status_codes[\$status] + 1))
        fi
    fi
done < "\$log_file"

## Display the status codes and their counts
for status in "\${!status_codes[@]}"; do
    case "\$status" in
        200) description="OK" ;;
        302) description="Found/Redirect" ;;
        401) description="Unauthorized" ;;
        404) description="Not Found" ;;
        500) description="Internal Server Error" ;;
        *) description="Other" ;;
    esac
    echo "Status \$status (\$description): \${status_codes[\$status]} requests"
done

echo "--------------------------------------"

## Analyze HTTP methods
echo "HTTP Methods:"
echo "--------------------------------------"
declare -A methods

while read -r line; do
    ## Extract the HTTP method
    request=\$(echo "\$line" | awk -F'"' '{print \$2}')
    method=\$(echo "\$request" | awk '{print \$1}')
    
    ## Count occurrences of each method
    if [ -n "\$method" ]; then
        if [ -z "\${methods[\$method]}" ]; then
            methods[\$method]=1
        else
            methods[\$method]=\$((methods[\$method] + 1))
        fi
    fi
done < "\$log_file"

## Display the HTTP methods and their counts
for method in "\${!methods[@]}"; do
    echo "\$method: \${methods[\$method]} requests"
done

echo "--------------------------------------"

## Identify requested resources
echo "Top requested resources:"
echo "--------------------------------------"
declare -A resources

while read -r line; do
    ## Extract the requested URL
    request=\$(echo "\$line" | awk -F'"' '{print \$2}')
    resource=\$(echo "\$request" | awk '{print \$2}')
    
    ## Count occurrences of each resource
    if [ -n "\$resource" ]; then
        if [ -z "\${resources[\$resource]}" ]; then
            resources[\$resource]=1
        else
            resources[\$resource]=\$((resources[\$resource] + 1))
        fi
    fi
done < "\$log_file"

## Display the resources
for resource in "\${!resources[@]}"; do
    echo "\$resource: \${resources[\$resource]} requests"
done

echo "--------------------------------------"

## Find error requests
echo "Error Requests (4xx and 5xx):"
echo "--------------------------------------"
error_count=0

while read -r line; do
    ## Extract the status code and URL
    status=\$(echo "\$line" | awk '{print \$9}')
    request=\$(echo "\$line" | awk -F'"' '{print \$2}')
    resource=\$(echo "\$request" | awk '{print \$2}')
    ip=\$(echo "\$line" | awk '{print \$1}')
    
    ## Check if status code begins with 4 or 5 (client or server error)
    if [[ "\$status" =~ ^[45] ]]; then
        echo "[\$status] \$ip requested \$resource"
        error_count=\$((error_count + 1))
    fi
done < "\$log_file"

if [ \$error_count -eq 0 ]; then
    echo "No error requests found."
fi

echo "======================================"
echo "Enhanced analysis complete!"
EOF

스크립트를 실행 가능하게 만들고 실행합니다.

chmod +x enhanced_log_analyzer.sh
./enhanced_log_analyzer.sh

이 향상된 스크립트는 사용된 HTTP 메서드 및 오류 요청 목록을 포함하여 추가적인 통찰력을 제공합니다.

스크립트가 명령줄 인수를 허용하도록 만들기

마지막으로, 스크립트가 명령줄 인수로 로그 파일 경로를 허용하도록 수정하여 더 다양하게 만들어 보겠습니다.

cat > log_analyzer_cli.sh << EOF
#!/bin/bash

## Log analyzer that accepts a log file path as command-line argument
## Usage: ./log_analyzer_cli.sh <log_file_path>

## Check if log file path is provided
if [ \$## -eq 0 ]; then
    echo "Error: No log file specified"
    echo "Usage: \$0 <log_file_path>"
    exit 1
fi

log_file="\$1"

## Check if the specified file exists
if [ ! -f "\$log_file" ]; then
    echo "Error: File '\$log_file' does not exist"
    exit 1
fi

echo "Log File Analysis: \$log_file"
echo "======================================"

## Count total number of entries
total_entries=\$(wc -l < "\$log_file")
echo "Total log entries: \$total_entries"
echo "--------------------------------------"

## Count unique IP addresses
echo "Unique IP addresses:"
echo "--------------------------------------"
unique_ips=0
declare -A ip_count

while read -r line; do
    ## Extract IP address (first field in each line)
    ip=\$(echo "\$line" | awk '{print \$1}')
    
    ## Count occurrences of each IP
    if [ -n "\$ip" ]; then
        if [ -z "\${ip_count[\$ip]}" ]; then
            ip_count[\$ip]=1
            unique_ips=\$((unique_ips + 1))
        else
            ip_count[\$ip]=\$((ip_count[\$ip] + 1))
        fi
    fi
done < "\$log_file"

## Display the IP addresses and their counts
for ip in "\${!ip_count[@]}"; do
    echo "\$ip: \${ip_count[\$ip]} requests"
done

echo "--------------------------------------"
echo "Total unique IP addresses: \$unique_ips"
echo "--------------------------------------"

## Count HTTP status codes
echo "HTTP Status Code Distribution:"
echo "--------------------------------------"
declare -A status_codes

while read -r line; do
    ## Extract status code (9th field in typical Apache log format)
    status=\$(echo "\$line" | awk '{print \$9}')
    
    ## Count occurrences of each status code
    if [ -n "\$status" ]; then
        if [ -z "\${status_codes[\$status]}" ]; then
            status_codes[\$status]=1
        else
            status_codes[\$status]=\$((status_codes[\$status] + 1))
        fi
    fi
done < "\$log_file"

## Display the status codes and their counts
for status in "\${!status_codes[@]}"; do
    case "\$status" in
        200) description="OK" ;;
        302) description="Found/Redirect" ;;
        401) description="Unauthorized" ;;
        404) description="Not Found" ;;
        500) description="Internal Server Error" ;;
        *) description="Other" ;;
    esac
    echo "Status \$status (\$description): \${status_codes[\$status]} requests"
done

echo "======================================"
echo "Analysis complete!"
EOF

스크립트를 실행 가능하게 만들고 액세스 로그 파일로 테스트합니다.

chmod +x log_analyzer_cli.sh
./log_analyzer_cli.sh access.log

스크립트는 이전 예제와 유사한 출력을 생성해야 하지만 이제 명령줄 인수로 지정된 모든 로그 파일을 분석할 수 있으므로 더 유연합니다.

결론

이 단계에서는 이전 단계에서 배운 파일 처리 기술을 적용하여 실용적인 로그 분석 도구를 만들었습니다. 이는 Bash 가 로그 파일과 같은 텍스트 파일을 처리하고 분석하는 데 얼마나 강력한지를 보여줍니다.

다음 방법을 배웠습니다.

  1. 구조화된 로그 파일에서 정보를 구문 분석하고 추출합니다.
  2. 로그 파일의 다양한 요소를 계산하고 분석합니다.
  3. 인수를 허용하는 유연한 명령줄 도구를 만듭니다.

이러한 기술은 로그 분석 외에도 광범위한 파일 처리 작업에 적용하여 Bash 스크립팅 및 파일 처리에 더 능숙하게 만들 수 있습니다.

요약

"Bash 에서 파일의 줄을 반복하는 방법" 튜토리얼을 완료하신 것을 축하드립니다. 이 랩을 통해 Bash 스크립트에서 파일을 줄 단위로 처리하는 데 필요한 기술을 익혔으며, 텍스트 처리, 로그 분석 및 일반적인 파일 처리에 유용한 기술을 습득했습니다.

주요 내용

  1. 기본 Bash 스크립팅: 셰뱅 라인 (shebang lines) 과 주석을 포함한 적절한 스크립트 구조를 포함하여 Bash 스크립트를 만들고 실행하는 방법을 배웠습니다.

  2. 파일을 줄 단위로 읽기: 파일 줄을 반복하는 두 가지 주요 접근 방식을 살펴보았습니다.

    • 다양한 파일 형식과 특수 문자를 처리하는 데 가장 강력한 접근 방식인 while read 메서드
    • 간결하지만 줄의 무결성을 유지하기 위해 특별한 처리가 필요한 for 루프 메서드
  3. 특수 사례 처리: 다음과 같은 예외 사례를 처리하는 기술을 배웠습니다.

    • 빈 줄
    • 특수 문자가 있는 파일
    • 구분된 파일 (예: CSV)
    • 큰 파일
  4. 실용적인 응용: 이러한 기술을 적용하여 웹 서버 로그에서 정보를 추출하고 요약하는 로그 파일 분석기를 만들었습니다.

다음 단계

Bash 스크립팅 기술을 더욱 향상시키려면 다음을 살펴보세요.

  1. 고급 텍스트 처리: 더 강력한 텍스트 처리 기능을 위해 awk, sed, grep과 같은 도구에 대해 자세히 알아보세요.

  2. 오류 처리: 스크립트에 더 강력한 오류 처리 및 유효성 검사를 구현합니다.

  3. 성능 최적화: 매우 큰 파일의 경우 처리 속도와 효율성을 향상시키는 기술을 살펴보세요.

  4. 자동화: 새로운 기술을 사용하여 일상적인 작업 흐름에서 반복적인 작업을 자동화합니다.

Bash 에서 이러한 파일 처리 기술을 마스터함으로써 이제 Linux 환경에서 텍스트 데이터를 처리할 수 있는 강력한 도구 세트를 갖게 되었습니다. 이러한 기술은 더 발전된 셸 스크립팅 및 시스템 관리 작업의 견고한 기반을 형성합니다.