Profile picture

[Shell Script] 파일 대조 스크립트 작성하기

JaehyoJJAng2024년 06월 09일

개요

관리하는 파일에 고유 식별 번호가 존재한다. 그런데 어떠한 이유로 인해 파일에 있는 고유 식별 번호가 중복되어있는 문제가 발생하였다.

관리 파일들은 management 디렉토리 및 그 하위 디렉토리에 .txt 확장자로 존재한다.

그러나 매번 중복되는 식별 번호를 수작업으로 찾는 것은 한계가 있다고 판단해 스크립트르 작성해보려고 한다.


요구사항 정의

해결하고자 하는 문제는 다음과 같다.

  • management 디렉토리에는 다양한 확장자의 파일이 존재함. .txt 파일 외의 파일들은 검사 대상에서 제외.
  • .txt 파일의 첫 줄에는 항상 숫자; 형식의 고유 식별 번호가 붙어있음.
    • Ex. 00001;
  • 하위 디렉토리(management/dir 등)를 포함한 모든 .txt 파일에서 식별 번호의 중복 여부를 확인해야 함.
  • 중복된 식별 번호가 발견되면 해당 파일의 경로를 출력

스크립트 설명

아래 코드들은 요구사항을 해결하기 위한 Bash 스크립트이다.

전체 코드를 먼저 살펴본 후, 한 줄씩 자세히 분석해보자.

#!/bin/bash
set -e

declare -A identifier_to_filepath
declare -A duplicate_identifiers

shopt -s globstar nullglob

for file in data/**/*.txt ; do
    first_line="$(head -n 1 "$file")"
    file_id="${first_line::-1}"
    file_path="$(dirname "$file")/$(basename "$file")"

    if [[ ${identifier_to_filepath[$file_id]} == "" ]]; then
        identifier_to_filepath[$file_id]="$file_path"
        continue
    fi

    if [[ ${duplicate_identifiers[$file_id]} == "" ]]; then
        duplicate_identifiers[$file_id]="- ${identifier_to_filepath[$file_id]}"
    fi

    duplicate_identifiers["$file_id"]+=$'\n'
    duplicate_identifiers["$file_id"]+="- $file_path"
done

shopt -u nullglob

if [[ ${#duplicate_identifiers[@]} == 0 ]]; then
    echo "No Duplication found"
    exit 0
fi

for id in "${!duplicate_identifiers[@]}"; do
    echo
    echo "ERROR: Duplicate id ${id} found"
    echo "${duplicate_identifiers[$id]}"
done

exit 1

코드 분석

1. 스크립트 시작과 옵션 설정

#!/bin/bash
set -e
  • set -e: 명령어 실행 중 하나라도 실패하면 스크립트가 즉시 종료되도록 설정한다. 중간에 오류가 발생했을 때 스크립트가 계속 실행되는 것을 방지할 수 있다.

2. 배열 선언

declare -A identifier_to_filepath
declare -A duplicate_identifiers
  • declare -A 명령어를 사용하여 두 개의 연관 배열을 선언한다. Bash에서는 기본 배열이 인덱스 기반이지만, -A 옵션을 사용하면 키-값 쌍으로 이루어진 연관 배열을 사용할 수 있다.
  • id_to_file_path: 각 파일의 식별 번호를 키로 하고, 해당 파일의 경로를 값으로 저장한다.
  • duplicate_file_paths: 중복된 식별 번호를 키로 하고, 해당 번호를 가진 파일들의 경로 목록을 값으로 저장한다.

3. 셸 옵션 설정

shopt -s globstar nullglob
  • shopt -s globstar: **를 사용하여 모든 하위 디렉터리를 재귀적으로 검색할 수 있도록 설정한다.
  • shopt -s nullglob: 글로빙 패턴이 일치하는 파일이 없을 경우, 패턴 자체를 그대로 문자열로 사용하지 않고 빈 목록으로 처리하도록 한다.

4. 파일 탐색과 식별 번호 추출

for file in management/**/*.txt ; do
    first_line="$(head -n 1 "$file")"
    file_id="${first_line::-1}"
    file_path="$(dirname "$file")/$(basename "$file")"
  • for file in data/**/*.txt; do: data 디렉터리와 하위 디렉터리의 모든 .txt 파일을 탐색한다.
  • first_line="$(head -n 1 "$file")": 파일의 첫 번째 줄을 읽어 first_line 변수에 저장한다.
  • file_id="${first_line::-1}": 첫 줄의 마지막 문자(;)를 제거하여 file_id에 저장한다.
  • file_path="$(dirname "$file")/$(basename "$file")": 파일의 디렉터리와 파일 이름을 결합해 파일 경로를 저장한다.

5. 중복 검사 및 배열 업데이트

    if [[ ${identifier_to_filepath[$file_id]} == "" ]]; then
        id_to_file_path[$file_id]="$file_path"
        continue
    fi

    if [[ ${duplicate_identifiers[$file_id]} == "" ]]; then
        duplicate_file_paths[$file_id]="- ${identifier_to_filepath[$file_id]}"
    fi

    duplicate_identifiers["$file_id"]+=$'\n'
    duplicate_identifiers["$file_id"]+="- $file_path"
  • if [[ ${identifier_to_filepath[$file_id]} == "" ]]; then: 식별 번호가 아직 identifier_to_filepath 배열에 없으면 추가하고 다음 파일로 넘어간다.
  • 중복이 발견된 경우, 해당 식별 번호를 duplicate_identifiers 배열에 저장한다. 처음 발견된 파일 경로를 목록에 추가하고, 이후 발견된 파일 경로들도 추가한다.

6. 옵션 해제

shopt -u nullglob
  • nullglob 옵션을 해제하여 이후 스크립트의 동작에 영향을 주지 않도록 한다.

7. 중복 검사 결과 출력

if [[ ${#duplicate_identifiers[@]} == 0 ]]; then
    echo "No Duplication found"
    exit 0
fi

for id in "${!duplicate_identifiers[@]}"; do
    echo
    echo "ERROR: Duplicate id ${id} found"
    echo "${duplicate_identifiers[$id]}"
done

exit 1
  • if [[ ${#duplicate_identifiers[@]} == 0 ]]; then: duplicate_identifiers 배열에 요소가 없으면, 중복이 없다는 메시지를 출력하고 스크립트를 정상 종료한다.
  • 중복된 식별 번호가 있으면 해당 번호와 중복된 파일 경로를 출력하고, 스크립트를 오류 코드 1로 종료한다.

실행 예제

management 디렉토리에 다음과 같은 파일 구조가 있다고 가정해보자.

data/
├── a.txt    # 첫 줄: 00001;
├── b.txt    # 첫 줄: 00002;
├── dir/
│   ├── c.txt  # 첫 줄: 00001;
│   └── d.txt  # 첫 줄: 00003;

스크립트를 실행하면 a.txt와 c.txt의 식별 번호가 중복되었다는 오류 메시지가 출력될 것이다.


실행 결과

ERROR: Duplicate id 00001 found
- data/a.txt
- data/dir/c.txt

Loading script...