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).