Journal Un petit script pour sauvegarder rapidement un fichier

Posté par (page perso) . Licence CC by-sa
5
23
mar.
2013

Quand je développe, il m'arrive régulièrement d'avoir sous la main des fichiers que je ne veux pas perdre dans l'immédiat, sans pour autant vouloir les ajouter dans mon logiciel de contrôle de versions. De même, il m'arrive de vouloir temporairement garder une image d'un fichier (avant de tester quelque chose, par exemple), sans pour autant vouloir créer un commit.

Bref, je souhaite pouvoir sauvegarder temporairement un fichier sans effort.

Pour répondre à ce besoin, j'ai créé le petit script suivant qui copie tout simplement les fichiers qu'on lui passe sur la ligne de commande dans un répertoire poubelle, ~/tmp par défaut. Si le nom du fichier existe déjà dans le répertoire destination, on lui rajoute tout simplement un numéro (par exemple : toto.txt => toto_1.txt => toto_2.txt), ce qui évite d'écraser une sauvegarde existante. Quand dans l'immense majorité des cas la sauvegarde se révèle inutile, il n'y a rien de particulier à faire, si ce n'est vider le répertoire poubelle de temps en temps. Dans le cas contraire, le nom du fichier, le numéro de version et la date de modification devraient permettre de retrouver ses petits.

Sans plus attendre, voici la bête!

#!/usr/bin/env python3
# coding: utf-8
# Copyright Joel Schaerer, 2013. Licensed under the GPL, version 3

from datetime import datetime
from sys import argv
import os, shutil
from itertools import count

""" quicksave.py : Simple script for quick and dirty backups.

    Save all files given on the command line to save_dir.
    Will add _n to the filename if it already exists in
    the destination dir so as never to overwrite a file.
"""

SAVE_DIR = os.path.expanduser("~/tmp")

def make_name(dirname, basename, extention, cnt=None):
    """ Build the name for the backup """
    return os.path.join(dirname, "".join((basename,
                        "_%d" % cnt if cnt else "", ".", extention)))

for f in argv[1:]:
    dir, base = os.path.split(f)
    # We don't use os.path.splitext because it doesn't handle .tar.gz
    parts = base.split(".")
    name, ext = parts[0], ".".join(parts[1:])
    for n in count():
        save_name = make_name(SAVE_DIR, name, ext, n)
        if not os.path.exists(save_name):
            break
    shutil.copy(f, save_name)

Voilà, rien d'extraordinaire, mais je me dis que le principe peut être utile à d'autres gens. Les retours sont les bienvenus! J'ai publié une version sur github qui recevra les (éventuelles) modifications.

  • # git stash ?

    Posté par (page perso) . Évalué à 6.

    Hello,

    alors, je suis encore loin d'être habitué à Git pour le moment, mais n'est-ce pas là le rôle de «git stash», qui permet de sauvegarder des modifs temporairement ?

    alf.life

    • [^] # Re: git stash ?

      Posté par (page perso) . Évalué à 3.

      ça ressemble un peu, mais git stash ne sauve que les fichiers qui sont sous contrôle de version, pas les autres.

      Et puis j'utilise git à la maison, mercurial au boulot et parfois aussi rien du tout, donc c'est pas mal d'avoir un outil simple et indépendant du logiciel de contrôle de version utilisé.

      • [^] # Re: git stash ?

        Posté par . Évalué à 7.

        git stash ne sauve que les fichiers qui sont sous contrôle de version, pas les autres

        L'option --include-untracked est là pour ça.

      • [^] # Re: git stash ?

        Posté par . Évalué à 3.

        pour mercurial, il me semble que tu peux très bien t'en sortir avec les mqueue. À confirmer par un spécialiste.

    • [^] # Re: git stash ?

      Posté par . Évalué à 7.

      Si. Tu peux aussi mettre le travail en cours dans une branche dédiée. C'est fait pour. Savoir maitriser son VCS est vraiment quelque chose de très important. "Ne pas vouloir créer un commit" est aberrant de nos jours. Si ca fait du sens à l'instant T de commiter en local alors il faut commiter même si on sait qu'à long terme il va disparaitre. On pourra toujours le retravailler, le supprimer, le combiner ou le déplacer si besoin.

      Je n'arrive pas a voir le cas d'utilisation de quelque chose que l'on "veut garder" mais dont on se fou puisque si jamais la machine crash adieu le /tmp.

      • [^] # Re: git stash ?

        Posté par (page perso) . Évalué à 0.

        C'est pas parce que le VCS est un outil puissant qu'on est obligé de l'utiliser pour tout.

        Créer des mini commits toutes les cinq minutes ne sert à rien, pourrit l'historique, est beaucoup plus verbeux, etc. Mais bon, à chacun le sien.

        • [^] # Re: git stash ?

          Posté par . Évalué à 4.

          Créer des mini commits toutes les cinq minutes ne sert à rien, pourrit l'historique, est beaucoup plus verbeux, etc.

          À la fin ca ne pourri rien du tout. Si tes commits ne servent à rien au final, ils ne seront simplement plus là. Ils t'ont juste servi temporairement.

          Maintenant l'avantage c'est que tu as une chronologie, une séparation de ton travail et une place qui centralise tout. Tu peux t'arrêter de bosser n'importe quand, et reprendre n'importe quand c'est toujours aussi clair. Les changements sont toujours répertoiriés et dissociés.

          La tera-chiée de foo_${id} dans un répertoire, ca devient vite compliqué de s'y retrouver vu qu'il n'y a aucun lien logique. Pourtant tu les as bien sauvegardé pour une raison ?

        • [^] # Re: git stash ?

          Posté par . Évalué à 4.

          Tu n'es pas obligé de push ça où tu dois push ton travail, tu peux le faire sur un autre dépôt.

          Tu te fais une branche sur laquelle tu fais ce que tu veux et que tu push dans un dépôt perso ou du moins privé, avec des commits inutiles toutes les 2 minutes puis tu fais un « git reset --merge tonderniercommitutile » et tu commit tout ce que tu as fait entre-temps, ensuite tu push simplement là où tu dois push pour ton boulot.
          C'est simple à faire, je fais ça tout le temps.

        • [^] # Commentaire supprimé

          Posté par . Évalué à 5.

          Ce commentaire a été supprimé par l'équipe de modération.

          • [^] # Re: git stash ?

            Posté par (page perso) . Évalué à 3. Dernière modification le 23/03/13 à 15:54.

            Je parle justement de verbosité en termes de nombre de commandes à tapper. qs truc.txt sera toujours plus rapide et simple que de faire un commit sur une branche locale, passer sur la branche principale, squasher les commits, merger, etc.

            Après c'est une histoire de workflow, je comprends très bien que certains préfèrent tout faire avec git. Ce n'est pas mon cas.

            • [^] # Re: git stash ?

              Posté par . Évalué à 1. Dernière modification le 24/03/13 à 11:34.

              Allez, puisqu'on y est, je propose aussi une solution avec la syntaxe qs truc.txt

              #!/bin/bash
              git checkout -b `date +"tmp_%Y%m%d_%H%M%S"` && git add $1 && git commit -m temporary && git checkout -
              
              

              Ce qui va créer une branche tmp_<date>, y ajouter le fichier passé en paramètre, commiter, et revenir sur la branche précédente.

              Hassan Céhef

              • [^] # Re: git stash ?

                Posté par (page perso) . Évalué à 2.

                Pas mal! Par contre il faudra aussi un petit script pour supprimer toutes ces branches de temps en temps, sinon git branch va vite devenir illisible :-)

        • [^] # Re: git stash ?

          Posté par (page perso) . Évalué à 2.

          C'est pas parce que le VCS est un outil puissant qu'on est obligé de l'utiliser pour tout.

          Ayant eu un jour le même besoin que toi je suis arrivé à la conclusion que ce que le programme dont j'avais besoin était… un VCS!

          Si tu ne veux pas mélanger tes fichiers pas très importants à ceux de ton projet principal, le plus simple est de le placer dans un autre dossier et de les gérer avec un autre repository. Avec quelques alias, variables du shell ou des liens symboliques, cela devrait se faire assez facilement.

      • [^] # Re: git stash ?

        Posté par . Évalué à 3.

        Sauf qu'il n'utilise pas /tmp…

        Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

  • # Ou sinon

    Posté par . Évalué à 10. Dernière modification le 23/03/13 à 15:02.

    #!/bin/sh
    # licensed under: rien à branler
    cp --backup=numbered "$1" ~/tmp/"${1##*/}"
    
    
    • [^] # Re: Ou sinon

      Posté par (page perso) . Évalué à 3. Dernière modification le 23/03/13 à 15:02.

      Ah, magnifique, je me doutais que ça devait exister :) Est-ce que ça marche aussi avec plusieurs fichiers?

      • [^] # Re: Ou sinon

        Posté par . Évalué à 4.

        Par contre --backup est spécifique à GNU cp.

      • [^] # Re: Ou sinon

        Posté par . Évalué à 6.

        Ah, plusieurs fichiers, j'avais pas pensé

        #!/bin/sh
        cp --backup=numbered "$@" ~/tmp
        
        
        • [^] # Re: Ou sinon

          Posté par (page perso) . Évalué à 1.

          Par contre ça change l'extension du fichier ce qui me gène un peu.

          • [^] # Re: Ou sinon

            Posté par (page perso) . Évalué à 6.

            il est facile en bash de récupérer soit le nom de ficher soit l'extension avec ça :

            FILE="example.tar.gz"
            ~% echo "${FILE%%.*}"
            example
            ~% echo "${FILE#*.}"
            tar.gz
            
            

            À partir de ça, on peut caser le numéro ou une date où on veut. Mais je préfère finalement avoir la date à la fin, même si ça "perturbe" l'extension, les cas où on a besoin de récupérer un fichier sont peu nombreux, et au pire des cas on peut aussi faire un script pour effacer automatiquement l'extension avec date autogénérée.

            Je préfère également avoir ce genre de chose dans un alias de mon ~/.bashrc, c'est plus pratique que dans un script en python je trouve.

            « I approve of any development that makes it more difficult for governments and criminals to monopolize the use of force. » Eric Raymond

  • # banana.split()

    Posté par (page perso) . Évalué à 9.

    parts = base.split(".")
    name, ext = parts[0], ".".join(parts[1:])
    
    

    Cette partie de code est très moche. split() peut prendre un deuxième argument pour limiter le nombre de coupes:

    >>> 'foobar.tar.gz'.split('.', 1)
    ['foobar', 'tar.gz']
    
    

    Sinon y a aussi partition() qui est sympa.

  • # heu....

    Posté par . Évalué à 4. Dernière modification le 23/03/13 à 15:12.

    Salut,

    le même en 2 lignes multi-fichiers et horodaté …
    cat ./bk.sh

    #!/bin/bash
    cp $1 ~/tmp/$1.`date %F_%H:%M`


    usage :

    bk.sh ./test.out

    ls ~/tmp
    test.out.2013-03-23_15:07
    test.out.2013-03-23_15:09

    • [^] # Re: heu....

      Posté par (page perso) . Évalué à 1.

      Un seul fichier, ne gère pas les espaces, pourrit l'extension du fichier, etc. etc.

      The devil is in the details qu'on dit :)

      • [^] # Re: heu....

        Posté par (page perso) . Évalué à 3.

        J'ai un script qui gère ce genre de choses mais qui ne répond pas à tous tes critères vu que l'horodatage est collé après l'extension du fichier :

        ellendhel@slackware:~$ touch test.ext
        ellendhel@slackware:~$ ls -lh test.ext 
        -rw-r--r-- 1 ellendhel users 0 Mar 23 10:25 test.ext
        ellendhel@slackware:~$ mkbackup test.ext 
        `test.ext' -> `test.ext-2013-03-23-01'
        ellendhel@slackware:~$ mkbackup test.ext 
        `test.ext' -> `test.ext-2013-03-23-02'
        ellendhel@slackware:~$ 
        
        

        J'ai choisi volontairement de "casser" l'extension, de fait on ne peut pas ouvrir le fichier sauvegardé en double-cliquant dessus par accident. Et si le fichier à le droit d'exécution, le droit est révoqué sur les copies. Il y a également un bout de code qui permet d'ajouter cette fonction dans Dolphin au besoin.

        C'est disponible ici et .

      • [^] # Re: heu....

        Posté par (page perso) . Évalué à 5.

        Hop, c'est servi !

        DATE=`date +%Y-%m-%d_%H-%M-%S`
        IFS=$'\n'
        for I in $@; do 
            echo cp "$I" "$HOME/tmp/$I.$DATE"
        done
        
        

        J'ai modifié le format de date par rapport au posteur original : Je n'aime pas trop les ":" dans les noms de fichiers. De plus, il me semble que c'est incompatible avec les Mac (comme l'ai le "\" pour les Windows)

  • # rdiff-backup

    Posté par (page perso) . Évalué à 4.

    Puisque tout le monde y va de sa solution, j'y vais de la mienne : l'excellent rdiff-backup fait plus ou moins ce que tu cherches.

    La syntaxe:

    rdiff-backup /mon/fichier/ou/mon/dossier /mon/repertoire/de/backup
    
    

    Tu peux le faire autant de fois que tu veux, toutes les anciennes versions sont gardées. Bon par contre les anciennes versions sont chainées, donc si un diff au milieu de la chaine est cassée, le reste de la chaine est invalide… mais pour du quick'n'dirty, ça devrait aller.

    • [^] # Re: rdiff-backup

      Posté par . Évalué à 2.

      et si ça diff pas (binaire) ?

      • [^] # Re: rdiff-backup

        Posté par (page perso) . Évalué à 1.

        Que veux-tu dire par là ? rdiff-backup utilise rdiff, qui ne s'occupe pas du contenu et considère que tout est binaire.

        • [^] # Re: rdiff-backup

          Posté par . Évalué à 2.

          J'ai pas compris le truc de la chaîne de diff alors.

          • [^] # Re: rdiff-backup

            Posté par (page perso) . Évalué à 1.

            Ah. En fait le fonctionnement de rdiff-backup, quand un historique existe deja, c'est de faire le diff entre la version courante et la version précédente, et de stocker ce diff. Comme ça, pour revenir a la version précédente, tu reappliques le diff a la version courante; pour aller a la version avant, tu applique le diff suivant, etc..

            Du coup, si un diff est casse au milieu de ton historique, tu ne pourras pas aller plus loin.

  • # Quitte à se faire égorger plus tard

    Posté par . Évalué à -2.

    C'est pas libre, c'est pas hébergé chez nous, mais pour des choses qui ne craignent pas, Dropbox sauvegarde automatiquement tous les changements effectués aux fichiers de notre Dropbox. Comme ça on peut revenir en arrière en cas de pépin.
    Par contre je ne pense pas que ça tienne toujours quand on écrase un fichier avant que ce dernier ait eu le temps d'être uploadé.

    C'est vraiment trop lourd comme solution quand on veut juste éditer un fichier, mais si on a déjà une dropbox dans un coin c'est bon à savoir.

    • [^] # Re: Quitte à se faire égorger plus tard

      Posté par . Évalué à 5.

      Si tu vas par là, en combinant un inotifywait ou watchmedo avec un rsync --link-dest tu as la même solution en 4 lignes sauf que ca juste marche exactement comme tu le veux et en local…

      • [^] # Re: Quitte à se faire égorger plus tard

        Posté par (page perso) . Évalué à 2.

        Si tu vas par là, plutôt qu'utiliser une solution bricolée à la main, je pense que tu peux te reposer sur des outils faits pour

        • [^] # Re: Quitte à se faire égorger plus tard

          Posté par (page perso) . Évalué à 2.

          Est-ce qu'il peut garder un historique des fichiers? Dropbox garde une vingtaine de versions, c'est bien pratique.

          • [^] # Re: Quitte à se faire égorger plus tard

            Posté par (page perso) . Évalué à 3.

            lsyncd n'est rien d'autre qu'un outil pour surveiller un dossier ou un fichier, agréger les événements et les envoyer à ton utilitaire de "déplacement". La gestion des versions, elle, dépend de cet outil. rsync par défaut ne gère pas les versions, par contre rsync --link-dest, avec les bons arguments, si. Mais, là encore, en allant dans cette direction tu vas te retrouver avec des scripts bricolés à la main que toi seul peut comprendre, maintenir et débugger. Je conseillerai plutôt de lier ça avec rdiff-backup que j'ai lié plus tôt, et qui s'occupe de la gestion des versions; il y a aussi duplicity qui a l'avantage de prendre en compte le chiffrement des données. Il y a aussi des dizaines d'autres outils, mais on commence à avoir quelque chose de lourd après =]

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.