Je dois créer une sauvegarde script (bash) d'une base de données MySQL. Lorsque j'exécute le script, un fichier sql sera créé dans "/home/user/Backup". Le problème est que je dois aussi faire un script qui supprime le fichier le plus ancien s'il y a plus de 7 fichiers dans ".../Backup". Quelqu'un sait-il comment faire cela ? J'ai tout essayé, mais ça échoue à chaque fois à compter les fichiers dans le répertoire et à détecter le plus ancien...
Réponses
Trop de publicités?Introduction
Passons en revue le problème : la tâche consiste à vérifier si le nombre de fichiers dans un répertoire particulier est supérieur à un certain nombre, et à supprimer le fichier le plus ancien parmi ceux-ci. Au début, il peut sembler que nous devions parcourir l'arborescence du répertoire une fois pour compter les fichiers, puis la parcourir à nouveau pour trouver la date de dernière modification de tous les fichiers, les trier, et extraire le plus ancien pour le supprimer. Mais si l'on considère que dans ce cas particulier, l'OP a mentionné la suppression des fichiers si et seulement si le nombre de fichiers est supérieur à 7, cela suggère que nous pouvons simplement obtenir la liste de tous les fichiers avec leurs horodatages une fois, et les stocker dans une variable.
Le problème de cette approche est le danger associé aux noms de fichiers. Comme cela a été mentionné dans les commentaires, il n'est jamais recommandé de parser ls
car la sortie peut contenir des caractères spéciaux et briser un script. Mais , comme certains d'entre vous le savent peut-être, dans les systèmes de type Unix (et Ubuntu également), chaque fichier est associé à un numéro d'inode. Ainsi, la création d'une liste d'entrées avec des horodatages (en secondes pour un tri numérique facile) plus le numéro d'inode séparés par un saut de ligne garantira une analyse sûre des noms de fichiers. La suppression du nom de fichier le plus ancien peut également être effectuée de cette manière.
Le script présenté ci-dessous fait exactement ce qui est décrit ci-dessus.
script
Important : Veuillez lire les commentaires, en particulier dans delete_oldest
fonction.
#!/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 "$@"
Exemples d'utilisation
$ ./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
Discussion supplémentaire
C'est probablement effrayant et long. et semble en faire beaucoup trop. Et c'est possible. En fait, tout peut être regroupé sur une seule ligne de commande (une version très modifiée de la suggestion de muru postée dans chat qui traite des noms de fichiers. echo
est utilisé à la place de rm
à des fins de démonstration) :
find /home/xieerqi/bin/testdir/ -maxdepth 1 -type f -printf "%T@ %p\0" | sort -nz | { f=$(awk 'BEGIN{RS=" "}NR==2{print;next}' ); echo "$f" ; }
Cependant, il y a plusieurs choses que je n'aime pas à ce sujet :
- il supprime le fichier le plus ancien sans condition, sans vérifier le nombre de fichiers dans le répertoire.
- il traite directement les noms de fichiers (ce qui m'a obligé à utiliser une méthode maladroite
awk
qui ne fonctionnera probablement pas avec les noms de fichiers comportant des espaces). - trop de plomberie (trop de tuyaux)
Ainsi, bien que mon script semble horriblement gigantesque pour cette simple tâche, il effectue beaucoup plus de vérifications et vise à résoudre le problème des noms de fichiers complexes. Il serait probablement plus court et plus idiomatique de l'implémenter en Perl ou Python ( ce que je peux absolument faire, j'ai juste choisi par hasard bash
pour cette question ).
Je pense que la réponse de @Serg est bonne et j'apprends de lui et de @muru. J'ai fait cette réponse parce que je voulais explorer et apprendre comment créer un fichier shellscript basé sur la sortie de find
avec l'action -print
pour trier les fichiers en fonction de l'heure à laquelle ils ont été créés/modifiés. Veuillez suggérer des améliorations et des corrections de bogues (si nécessaire).
Comme vous le remarquerez, le style de programmation est très différent. Nous pouvons faire les choses de plusieurs façons dans linux :-)
J'ai fait un bash Shell-Shell pour répondre aux exigences du PO, @beginner27_, mais il n'est pas trop difficile de le modifier pour d'autres buts mais similaires.
La capture d'écran suivante montre comment il a été testé : Onze fichiers ont été créés, et le script (qui réside dans ~/bin et a les droits d'exécution) est exécuté. J'ai supprimé le caractère # de la ligne
# bash "$cmd"
pour le faire
bash "$cmd"
La première fois, le script découvre et imprime les onze fichiers, les sept plus récents sur fond bleu et les quatre plus anciens sur fond rouge. Les quatre fichiers les plus anciens sont supprimés. Le script est exécuté une deuxième fois (juste pour la démo). Il découvre et imprime les sept fichiers restants, et est satisfait, 'Il n'y a pas de fichier de sauvegarde à supprimer'.
Le point crucial find
qui trie les fichiers en fonction du temps, ressemble à ceci,
find "$bupdir" -type f -printf "%T+ %p\0"|sort -nrz > "$srtlst"
Voici le fichier script. Je l'ai enregistré dans ~/bin
avec le nom rm_old_backups
mais vous pouvez lui donner n'importe quel nom, tant qu'il n'interfère pas avec le nom existant d'un programme exécutable.
#!/bin/bash
keep=7 # set the number of files to keep
# variables and temporary files
inversvid="\0033[7m"
resetvid="\0033[0m"
redback="\0033[1;37;41m"
greenback="\0033[1;37;42m"
blueback="\0033[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\0"|sort -nrz > "$srtlst"
# more output
echo -e "${inversvid}time-stamp file-name $resetvid"
echo -en "$blueback"
sed -nz -e 1,"$keep"p "$srtlst" | tr '\0' '\n'
echo -en "$resetvid"
echo -en "$redback"
sed -z -e 1,"$keep"d "$srtlst" | tr '\0' '\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