Apaga o arquivo mais antigo em um diretório, quando há mais de 7 arquivos?

7

Eu tenho que criar um script de backup (bash) de um banco de dados MySQL. Quando executo o script, um arquivo sql será criado em "/ home / user / Backup". O problema é que eu também tenho que fazer um script que apague o arquivo mais antigo se houver mais de 7 arquivos em "... / Backup". Alguém sabe como fazer isso? Eu tentei de tudo, mas falhou todas as vezes para contar os arquivos no diretório e detectar o mais antigo ...

    
por beginner27_ 06.03.2017 / 21:00

2 respostas

4

Introdução

Vamos rever o problema: a tarefa é verificar se o número de arquivos em determinado diretório está acima de determinado número e excluir o arquivo mais antigo entre eles. A princípio, pode parecer que precisamos percorrer a árvore de diretórios depois de contar os arquivos, depois percorrê-los novamente para encontrar a hora da última modificação de todos os arquivos, classificá-los e extrair o mais antigo para exclusão. Mas considerando que neste caso particular OP mencionou deletar arquivos se e somente se o número de arquivos estiver acima de 7, isso sugere que podemos simplesmente obter uma lista de todos os arquivos com seus timestamps uma vez, e armazená-los em uma variável.

O problema com essa abordagem é o perigo associado aos nomes de arquivos. Como foi mencionado nos comentários, nunca é recomendado analisar o comando ls , pois a saída pode conter caracteres especiais e quebrar um script. Mas, como alguns de vocês podem saber, em sistemas semelhantes ao Unix (e também no Ubuntu), cada arquivo tem um número de inode associado a ele. Assim, criar uma lista de entradas com registros de data e hora (em segundos para facilitar a classificação numérica) mais o número de inode separado por nova linha garantirá a análise segura dos nomes dos arquivos. A exclusão do nome de arquivo mais antigo também pode ser feita dessa maneira.

O roteiro apresentado abaixo faz exatamente como descrito acima.

Script

Importante : Por favor, leia os comentários, especialmente na função 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 "$@"

Exemplos de uso

$ ./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

Discussão adicional

Isso provavelmente é assustador. . e demorado. . e parece que é demais. E pode ser. Na verdade, tudo pode ser colocado em uma linha única de linha de comando (uma versão muito modificada da sugestão de Muru postada em http://chat.stackexchange.com/rooms/55107/sort-files-by -modification-time-with-find "> chat que lida com nomes de arquivos. echo é usado em vez de rm para fins de demonstração):

find /home/xieerqi/bin/testdir/ -maxdepth 1 -type f -printf "%T@ %p
#!/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 "$@"
" | sort -nz | { f=$(awk 'BEGIN{RS=" "}NR==2{print;next}' ); echo "$f" ; }

No entanto, tenho várias coisas que não gosto disso:

  • exclui incondicionalmente o arquivo mais antigo, sem verificar o número de arquivos no diretório
  • lida com nomes de arquivos diretamente (o que me obrigou a usar o comando awkward awk , que provavelmente quebrará com nomes de arquivos que possuem espaços)
  • encanamento demais (tubos demais)

Assim, enquanto meu script parece horrivelmente gigantesco para a tarefa simples, ele faz muito mais verificações e visa resolver problemas com nomes de arquivos complexos. Provavelmente seria mais curto e idiomático para implementar em Perl ou Python (o que eu absolutamente posso fazer, por acaso escolhi bash para esta questão).

    
por Sergiy Kolodyazhnyy 12.03.2017 / 18:10
1

Eu acho que a resposta do Serg é boa e estou aprendendo com ele e com o @muru. Fiz essa resposta porque queria explorar e aprender como criar um arquivo shellscript com base na saída de find com a 'ação' -print para classificar os arquivos de acordo com a hora em que foram criados / modificados. Por favor sugira melhorias e correções de bugs (se necessário).

Como você notará, o estilo de programação é muito diferente. Nós podemos fazer as coisas de várias maneiras no linux: -)

Eu fiz um bash shell script para combinar com os requisitos do OP, @ beginner27_, mas não é muito difícil modificá-lo para outros propósitos similares.

A captura de tela a seguir mostra como ela foi testada: Onze arquivos foram criados e o script (que reside em ~ / bin e tem permissões de execução) é executado. Eu removi o caractere # da linha

# bash "$cmd"

para fazer isso

bash "$cmd"

A primeira vez que o script descobre e imprime os onze arquivos, os sete arquivos mais recentes com fundo azul e os quatro arquivos mais antigos com fundo vermelho. Os quatro arquivos mais antigos são removidos. O script é executado uma segunda vez (apenas para a demonstração). Ele descobre e imprime os sete arquivos restantes e está satisfeito: 'Não há arquivo de backup para remover'.

Ocomandofindcrucial,queclassificaosarquivosdeacordocomahora,éassim,

find "$bupdir" -type f -printf "%T+ %p#!/bin/bash keep=7 # set the number of files to keep # variables and temporary files inversvid="# bash "$cmd" 33[7m" resetvid="bash "$cmd" 33[0m" redback="find "$bupdir" -type f -printf "%T+ %p#!/bin/bash keep=7 # set the number of files to keep # variables and temporary files inversvid="%pre%33[7m" resetvid="%pre%33[0m" redback="%pre%33[1;37;41m" greenback="%pre%33[1;37;42m" blueback="%pre%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%pre%"|sort -nrz > "$srtlst" # more output echo -e "${inversvid}time-stamp file-name $resetvid" echo -en "$blueback" sed -nz -e 1,"$keep"p "$srtlst" | tr '%pre%' '\n' echo -en "$resetvid" echo -en "$redback" sed -z -e 1,"$keep"d "$srtlst" | tr '%pre%' '\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" 33[1;37;41m" greenback="%pre%33[1;37;42m" blueback="%pre%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%pre%"|sort -nrz > "$srtlst" # more output echo -e "${inversvid}time-stamp file-name $resetvid" echo -en "$blueback" sed -nz -e 1,"$keep"p "$srtlst" | tr '%pre%' '\n' echo -en "$resetvid" echo -en "$redback" sed -z -e 1,"$keep"d "$srtlst" | tr '%pre%' '\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"

Aqui está o arquivo de script. Salvei-o em ~/bin com o nome rm_old_backups , mas você pode atribuir um nome a ele, desde que não interfira em algum nome já existente de um programa executável.

%pre%     
por sudodus 12.03.2017 / 21:47

Tags