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

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

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

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

작업은 로그 끝에만 숫자를 늘리고 디렉터리의 나머지 내용은 변경하지 않고 그대로 두는 것입니다. 또한 각 로그 유형에 대해 빈 파일을 만듭니다.

예를 들어:

./
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) 회전되지 않은 파일 하나(숫자 접미사가 없는 파일)가 남을 수 있습니다. 그렇다면 파일 이름.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[@]}"

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

원래 질문에는 파일이 1,000개 미만이고 파일당 버전이 21개 미만이라고 나와 있었습니다. 이 숫자보다 많으면 어떻게 해야 하는지는 알려주지 않았습니다. 이 솔루션은 파일당 최대 100개 버전을 지원하며, 패턴 확장만으로 1000개 이상까지 확장할 수 있습니다.

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

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

관련 정보