Bash를 사용하여 덮어쓰지 않고 로그 파일 배치의 이름을 점진적으로 바꾸는 방법은 무엇입니까?

Bash를 사용하여 덮어쓰지 않고 로그 파일 배치의 이름을 점진적으로 바꾸는 방법은 무엇입니까?

내 솔루션이 초기 테스트 사례를 통과했지만 제출 시 50%의 시간 동안 실패하는 문제가 발생했습니다.

문제: 디렉터리에는 여러 파일과 폴더가 포함되어 있으며 그 중 일부는 error.log, error.log.1, error.log.2, access.log.1, access.log.2... 등 다양한 유형의 로그입니다. . 이 파일의 내용은 다음 날에 매핑되므로 "cat error.log.1"에는 "Next Day Log"..etc가 있습니다.

작업은 로그 끝에 있는 숫자만 증가시키고 디렉터리의 나머지 부분은 변경하지 않고 그대로 두는 것입니다. 또한 각 로그 유형에 대해 빈 파일을 만듭니다.

예를 들어:

./
example_dir
example2_dir
error.log
error.log.1
info.log.20
access.log.1
readme.txt

스크립트는 디렉터리를 다음으로 변경합니다.

./
example_dir (unchanged)
example2_dir (unchanged)
error.log (empty)
error.log.1 (originally error.log)
error.log.2 (originally error.log.1)
info.log (empty)
info.log.21 (originally info.log.20)
access.log (empty)
access.log.2 (originally access.log.1)
readme.txt (unchanged)

조건: # 디렉토리의 파일 < 1000, 각 유형의 최대 #파일 < 21

내 솔루션:

#!/bin/bash

declare -a filenames

# Renaming in ascending order will lead to overwrite; so start rename from the bottom

files=$(find . -maxdepth 1 -name "*.log.*" -exec basename {} \; | sort -rn)


for i in $files; do

    currentFileNumber=$(echo -e "$i" | sed -e 's/[^0-9]*//g') # Extract the current number from the filename
    fileName=$(echo -e "$i" | sed -e 's/\.[0-9]*$//g') # Extract the name without the trailing number

    newFileNumber=$(("$currentFileNumber" + 1)) # Increment the current number

    mv "$i" "$fileName.$newFileNumber" # Rename and append the incremented value

    if [[ ! ${filenames[*]} =~ ${fileName} ]] # Store names of existing types to create empty files
    then
        filenames=("${filenames[@]}" "${fileName}")
    fi
    # Could make use of [[ -e "$fileName.log" ]] instead of an array, but won't pass the test for some reason
done

for j in "${filenames[@]}"; do touch "$j"; done # Create the empty files
unset filenames

실패한 테스트 사례가 표시되지 않으므로 이 문제를 더 잘 해결하는 방법을 잘 모르겠습니다.

답변1

이것은 재미있는 연습이었으며 여기에 내 해결책이 있습니다.

#/bin/bash
log_names=$(for logfile in $(find . -type f -name '*.log*'); do echo ${logfile%.[0-9]*}; done | sort -u)

for name in $log_names; do
    echo "Processing $name"
    i=20
    until [[ "$i" -eq 0 ]]; do
        if [[ -f "$name.$i" ]]; then
            next_num=$((i+1))
            mv -v "$name.$i" "$name.$next_num"
        fi
        i=$((i-1))
    done
    if [[ -f "$name" ]]; then
        mv -v "$name" "$name.1"
    fi
    touch "$name"
done

log_names 변수는 find명령을 사용하여 로그 파일 목록을 가져옵니다. 그런 다음 문자열 대체를 적용하여 숫자 접미사를 제거합니다. 그런 다음 중복 항목을 정렬하고 제거합니다.

이 시점에서 다음 디렉터리에 고유한 로그 파일 이름 목록이 표시됩니다 ./access.log ./error.log ./info.log.

그런 다음 루프를 사용하여 각 이름을 차례로 처리합니다 for.

이제 각 파일에 대해 가능한 최대 개수는 20개라고 들었습니다. 여기서 시작하여 until루프를 사용하여 카운트다운합니다.

논리는 mv간단합니다. "filname.number"가 있으면 "filename.(number+1)"로 이동합니다.

루프가 완료 되면 until(i = 0) 숫자 접미사가 없는 회전되지 않은 파일이 남을 수 있습니다. 그렇다면 filename.1로 이동하세요.

마지막 단계는 빈 파일을 만드는 것입니다 touch.


실행 예:

$ ls
access.log.1  error.log  error.log.1  example_dir  example2_dir  info.log.20  readme.txt  rotate.bash
    
$ bash rotate.bash
Processing ./access.log
'./access.log.1' -> './access.log.2'
Processing ./error.log
'./error.log.1' -> './error.log.2'
'./error.log' -> './error.log.1'
Processing ./info.log
'./info.log.20' -> './info.log.21'

$ ls -1
access.log
access.log.2
error.log
error.log.1
error.log.2
example_dir
example2_dir
info.log
info.log.21
readme.txt
rotate.bash

답변2

@Haxiel이 솔루션을 게시했습니다. 이것은 내가 "가장 간단하다"고 생각하는 것과 유사합니다. 루프 for대신 루프 를 사용하겠습니다 until.

mv이는 기존 파일마다 하나씩, touch마지막에 새 파일을 생성하는 데 하나씩 , 거의 최소한의 외부 프로세스를 사용하는 것입니다 . (터치는 리디렉션을 사용하여 파일을 생성하는 루프로 대체되어 외부 프로세스 수를 1만큼 줄일 수 있습니다.)

#!/bin/bash
shopt -s nullglob # Reduce the number of things we have to work with

# get a list of the files we want to work with. 
files=( *.log *.log.[1-9] *.log.[1-9][0-9] )

# reverse the list into rfiles, getting rid of non-file things
rfiles=()
for ((i=${#files[@]}-1;i>=0;i--)) ; do
        if [ -f "${files[i]}" ] ; then
                rfiles+=("${files[i]}")
        fi
done

# exit early if there is nothing to do
if [ ${#rfiles[@]} -eq 0 ] ; then
        exit 0
fi

# an array of the files we need to create
typeset -A newfiles

# Loop over the reversed file list
for f in "${rfiles[@]}"; do
    # Get everything up to the last "log"
    baseName=${f%log*}log
    # Remove up to the last "log" and then the optional "."
    currentFileNum=${f#"$baseName"}
    currentFileNum=${currentFileNum#.}
    mv -v "$f" "$baseName.$((currentFileNum+1))"
    # record the name to make the new files
    newfiles[$baseName]=1
done

# Create all the needed new files, using the names stored in the array
touch "${!newfiles[@]}"

이 작업을 수행하는 순서는 모든 파일을 처리하는 대신 2자리 숫자가 있는 모든 파일을 먼저 이동한 다음 한 자리 숫자가 있는 모든 파일을 이동하고 마지막으로 ".log"로 끝나는 파일을 이동하는 @Haxiel의 솔루션에서 생성된 순서와 다릅니다. 첫 번째 부분이 동일한 파일이 함께 배치됩니다.

원래 질문에는 파일이 1000개 미만이고 각 파일의 버전이 21개 미만이라고 나와 있었습니다. 이 숫자를 초과하면 어떻게 해야 하는지는 나와 있지 않습니다. 이 솔루션은 파일당 최대 100개 버전을 지원하며 확장 모드만 사용하면 1000개 이상으로 확장할 수 있습니다.

파일 수는 bash에 사용 가능한 메모리 양에 따라 제한됩니다.

각 이름에 대해 N개의 파일을 시도하는 대신 존재하는 파일만 처리하려고 시도하므로 이것이 더 나은 솔루션이라고 생각합니다. N이 작은 경우(예: 21) 이는 중요하지 않습니다.

관련 정보