Удалить самый старый файл в каталоге, если имеется более 7 файлов?

7

Мне нужно создать резервный скрипт (bash) базы данных MySQL. Когда я запускаю скрипт, в файле / home / user / Backup создается файл sql. Проблема в том, что я также должен создать скрипт, который удаляет самый старый файл, если в «... / Backup» содержится более 7 файлов. Кто-нибудь знает, как это сделать? Я пробовал все, но он не мог каждый раз подсчитывать файлы в каталоге и обнаруживать самый старый ...

    
задан beginner27_ 06.03.2017 в 20:00
источник

2 ответа

4

Введение

Давайте рассмотрим проблему: задача состоит в том, чтобы проверить, превышает ли количество файлов в определенном каталоге определенное число, и удалить из них самый старый файл. Поначалу может показаться, что нам нужно пройти дерево каталогов после подсчета файлов, а затем снова пересечь его, чтобы найти последнее время модификации всех файлов, отсортировать их и извлечь самый старый для удаления. Но учитывая, что в этом конкретном случае OP упоминает удаление файлов тогда и только тогда, когда количество файлов превышает 7, это говорит о том, что мы можем просто получить список всех файлов с их отметками времени один раз и сохранить их в переменной.

Проблема с этим подходом - опасность, связанная с именами файлов. Как уже упоминалось в комментариях, никогда не рекомендуется разбирать команду ls , так как вывод может содержать специальные символы и прерывать скрипт. Но, как некоторые из вас могут знать, в Unix-подобных системах (и Ubuntu), каждый файл имеет номер inode, связанный с ним. Таким образом, создание списка записей с отметками времени (в секундах для простой цифровой сортировки) плюс число inode, разделенное символом новой строки, гарантирует, что мы безопасно проанализируем имена файлов. Удаление старого имени файла также может быть выполнено таким образом.

Сценарий, представленный ниже, выполняет точно так, как описано выше.

Сценарий

Важно . Пожалуйста, прочитайте комментарии, особенно в функции delete_oldest .

#!/bin/bash
# Uncomment line below for debugging
#set -xv
delete_oldest(){
     # reads a line from stdin, extracts file inode number
     # and deletes file to which inode belongs
     # !!! VERY IMPORTANT !!!
     # The actual command to delete file is commented out.
     # Once you verify correct execution, feel free to remove
     # leading # to uncomment it
     read timestamp file_inode
     find "$directory" -type f -inum "$file_inode"  -printf "Deleted %f\n" 
     # find "$directory" -type f -inum "$file_inode"  -printf "Deleted %f\n"  -delete
}

get_files(){
    # Wrapper function around get files. Ensures we're working
    # with files and only on one specific level of directory tree
    find "$directory" -maxdepth 1 -type f  -printf "%Ts\t%i\n" 
}

filecount_above_limit(){
    # This function counts number of files obtained
    # by get_files function. Returns true if file
    # count is greater than what user specified as max
    # value 
    num_files=$(wc -l <<< "$file_inodes"  )
    if [ $num_files -gt "$max_files" ];
    then
        return 0
    else
        return 1
    fi
}

exit_error(){
    # Print error string and quit
    printf ">>> Error: %s\n"  "$1" > /dev/stderr 
    exit 1
}

main(){
    # Entry point of the program. 
    local directory=$2
    local max_files=$1

    # If directory is not given
    if [ "x$directory" == "x"  ]; then
        directory="."
    fi

    # check arguments for errors
    [ $# -lt 1  ] && exit_error "Must at least have max number of files"
    printf "%d" $max_files &>/dev/null || exit_error "Argument 1 not numeric"
    readlink -e "$directory" || exit_error "Argument 2, path doesn't exist"

    # This is where actual work is being done
    # We traverse directory once, store files into variable.
    # If number of lines (representing file count) in that variable
    # is above max value, we sort numerically the inodes and pass them
    # to delete_oldest, which removes topmost entry from the sorted list
    # of lines.
    local file_inodes=$(get_files)
    if filecount_above_limit 
    then
        printf  "@@@ File count in %s is above %d." "$directory" $max_files
        printf "Will delete oldest\n"
        sort -k1 -n <<< "$file_inodes" | delete_oldest
    else
        printf "@@@ File count in %s is below %d."  "$directory" $max_files
        printf "Exiting normally"
    fi
}

main "[email protected]"

Примеры использования

$ ./delete_oldest.sh 7 ~/bin/testdir                                                                                     
/home/xieerqi/bin/testdir
@@@ File count in /home/xieerqi/bin/testdir is below 7.Exiting normally
$ ./delete_oldest.sh 7 ~/bin                                                                                             
/home/xieerqi/bin
@@@ File count in /home/xieerqi/bin is above 7.Will delete oldest
Deleted typescript

Дополнительная дискуссия

Это, наверное, страшно. , . и длительный. , . И похоже, что это слишком много. И может быть. Фактически, все может быть записано в одну строку командной строки (очень измененная версия предложения муру, опубликованная в chat , который обрабатывает имена файлов. echo используется вместо rm для демонстрационных целей):

find /home/xieerqi/bin/testdir/ -maxdepth 1 -type f -printf "%[email protected] %p%pr_e%" | sort -nz | { f=$(awk  'BEGIN{RS=" "}NR==2{print;next}'  ); echo "$f" ; }

Однако мне несколько вещей, которые мне не нравятся:

  • он удаляет старейший файл безоговорочно, не проверяя количество файлов в каталоге
  • он напрямую связан с именами файлов (что потребовало от меня использовать неудобную команду awk , которая, вероятно, будет ломаться с именами файлов с пробелами)
  • слишком много сантехники (слишком много труб)

Таким образом, хотя мой сценарий выглядит ужасно гигантским для простой задачи, он делает намного больше проверок и нацелен на решение проблемы со сложными именами файлов. Вероятно, было бы короче и более идиоматично реализовать в Perl или Python (что я абсолютно могу сделать, мне просто удалось выбрать bash для этого вопроса).

    
ответ дан Sergiy Kolodyazhnyy 12.03.2017 в 17:10
1

Я думаю, что ответ Сергея хороший, и я учусь от него и от @muru. Я сделал этот ответ, потому что хотел изучить и узнать, как создать файл shellscript на основе вывода find с «action» -print , чтобы сортировать файлы в зависимости от времени их создания / изменения. Пожалуйста, предложите улучшения и исправления (при необходимости).

Как вы заметили, стиль программирования сильно отличается. Мы можем многое делать в Linux: -)

Я создал shell-скрипт bash для соответствия требованиям OP, @ beginner27_, но его не так сложно изменить для других, но подобных целей.

Следующий скриншот показывает, как он был протестирован: одиннадцать файлов были созданы и запущен скрипт (который находится в ~ / bin и имеет разрешения на выполнение). Я удалил символ # из строки

# bash "$cmd"

, чтобы сделать это

bash "$cmd"

В первый раз, когда скрипт обнаруживает и печатает одиннадцать файлов, семь новейших файлов с синим фоном и четыре самых старых файла с красным фоном. Четыре старых файла удаляются. Сценарий запускается второй раз (только для демонстрации). Он обнаруживает и печатает оставшиеся семь файлов и удовлетворен: «Нет файла резервной копии для удаления».

Ключеваякомандаfind,сортирующаяфайлыповремени,выглядиттак:

find"$bupdir" -type f -printf "%T+ %p
#!/bin/bash

keep=7  # set the number of files to keep

# variables and temporary files

inversvid="%pr_e%33[7m"
resetvid="%pr_e%33[0m"
redback="%pr_e%33[1;37;41m"
greenback="%pr_e%33[1;37;42m"
blueback="%pr_e%33[1;37;44m"

bupdir="$HOME/Backup"
cmd=$(mktemp)
srtlst=$(mktemp)
rmlist=$(mktemp)

# output to the screen

echo -e "$inversvid$0:
keep $keep backup files, remove the oldest files, if more than $keep are found $resetvid"

echo "Security fix: You must edit this script and remove the # character from
a line near the end of the script '# bash \"\$cmd\"' --> 'bash \"\$cmd\"'
otherwise the script will only show what it can do. Please test that it
works correctly before you remove that # character!"

# the crucial find command, that sorts the files according to time

find "$bupdir" -type f -printf "%T+ %p%pr_e%"|sort -nrz > "$srtlst"

# more output

echo -e "${inversvid}time-stamp                     file-name                               $resetvid"
echo -en "$blueback"
sed -nz -e 1,"$keep"p "$srtlst" | tr '%pr_e%' '\n'
echo -en "$resetvid"

echo -en "$redback"
sed -z -e 1,"$keep"d "$srtlst" | tr '%pr_e%' '\n' | tee "$rmlist"
echo -en "$resetvid"

# remove oldest files if more files than specified are found

if test -s "$rmlist"
then
 echo rm '"'$(sed -z -e 1,"$keep"d -e 's/[^ ]* //' -e 's/$/" "/' "$srtlst")'"'\
 | sed 's/" ""/"/' > "$cmd"
 cat "$cmd"

# uncomment the following line to really remove files 
# bash "$cmd"

 echo "The oldest backup files are removed"
else
 echo "There is no old backup file to remove"
fi

# remove temporary files

rm $cmd $srtlst $rmlist
"|sort -nrz > "$srtlst"

Вот файл сценария. Я сохранил его в ~/bin с именем rm_old_backups , но вы можете указать ему любое имя, если оно не мешает уже существующему имени исполняемой программы. %pr_e%     

ответ дан sudodus 12.03.2017 в 20:47