2

x11, not Wayland

I often edit a variety of plain text files such as .css, .mkd, .sh, and .txt as well as .odt and .ods files.

All my files have

  • safe characters limited to [0-9a-zA-Z_]
  • no spaces in them
  • and have appropriate extensions.

Geany 1.32 comes with a customizable plugin to automatically save a timestamped backup to a destination of the user's choice. For example, a timestamped backup of ~/.bashrc looks like this:

/home/dkb/Public/GeanyBackups/.bashrc.2019-08-28-16-25-51

Geany allows timestamped backups

Gedit offered something similar years ago. Regaining this function may still be possible using the Manage External Tools… plugin. Answers here illustrate usage of this plugin.

As far as I know, other plain text editors such as featherpad, kate, leafpad and mousepad don't have such a feature. Even LibreOffice saves just one backup (if enabled in Tools > Options > LibreOffice > Load/Save).

It's possible to use rsync to backup files to timestamped folders but I want to capture backups of rather frequent edits in a destination of my choice.

I would like to have a script that allows me to perform a conventional save and immediately thereafter to generate a timestamped backup of a limited class of files I edit in my various text editors or in LibreOffice. I'd bind this script to a keyboard shortcut while leaving the conventional shortcuts still available.

4
  • Would be quite easy to create a tiny process that would create a time-stamped backup on every instance a file is saved or changed on disk, but you probably want to limit the number of max backups? Aug 28, 2019 at 11:26
  • 1
    It's by signal, not by polling, zero on resources, how else are you imagining it if it is not from within the application in question? Haven't been out for a day :), but I myself have been here as an unnoticed background process :) Aug 28, 2019 at 13:09
  • 1
    You should really look at a version management system such as Git.
    – xenoid
    Aug 28, 2019 at 13:09
  • @JacobVlijm, re. the number of backups, I don't mind removing older ones manually.
    – DK Bose
    Aug 28, 2019 at 13:12

2 Answers 2

2

Automatic time-stamped backups of a (any) specific file

If you are not afraid of compiling a vala file, the snippet below can be used. The advantage is that it is signal-based (using FileMonitor) and resource-consumption is zero. Furthermore, you don't need an extra manual action.

How it works

  • Just run the executable with the watched file and the targeted directory (to save the backups to) as arguments, e.g.

    '/home/jacob/Desktop/watch_out' '/home/jacob/Desktop/test' '/home/jacob/Desktop'
    

    Each time the file test is saved (so changed) on disk, a backup like:

    2019-08-29_09-14-26_test
    

    will be created.

    N.B just keep it running, it will automatically create the backups (!)

The code

/*
this snippet will watch a specific file (args[1]) for changes, make a time-
stamped copy into a set directory (args[2]). The advantage is that any file
can be watched, independent to the application it is edited with.
have fun!
Jacob Vlijm
*/

namespace watchout {

    File watched;
    string watched_fname;
    string watched_fpath;
    string target_fpath;

    public static void main(string[] args) {
        string watched_file = args[1];
        target_fpath = args[2];
        watched = File.new_for_path(watched_file);
        string[] fdata = getnamedata(watched_file);
        watched_fpath = fdata[0];
        watched_fname = fdata[1];
        MainLoop loop = new MainLoop();
        try {
            FileMonitor monitor = watched.monitor(FileMonitorFlags.NONE, null);
            monitor.changed.connect(dosomething);
            loop.run();
        }
        catch (Error e) {
        }
    }

    private string[] getnamedata (string watched_file) {
        string[] watched_data = watched_file.split("/");
        int len_data = watched_data.length;
        string path = string.joinv("/", watched_data[0:len_data - 1]);
        string fname = watched_data[len_data - 1];
        return {path, fname};
    }

    private void dosomething(
        File file, File? otherfile, FileMonitorEvent event
    ) {
        // event types:  https://valadoc.org/gio-2.0/GLib.FileMonitorEvent.html
        if (event == FileMonitorEvent.CHANGES_DONE_HINT) {
            makebackup();
        }
    }

    private void makebackup () {
        var now = new DateTime.now_local();
        string datestring = now.format("%Y-%m-%d_%H-%M-%S");
        string targetstring = target_fpath.concat("/", datestring, "_", watched_fname);
        File target = File.new_for_path(targetstring);
        try {
            watched.copy (target, 0, null, () => {
            });
        } catch (Error e) {
            print ("Error: %s\n", e.message);
        }
    }
}

Setup

  1. Install valac(to be able to compile)

    sudo apt install valac
    
  2. Copy the snippet above into an empty file, save is as watchout.vala

  3. Compile it with:

    valac --pkg gio-2.0 /path/to/watchout.vala
    
  4. Run it as described in How it works
4
  • Yes, but if I want to edit another file, I'll then have to enter a command for that as well? Also, instead of watch_out, I had to use watchout.
    – DK Bose
    Aug 29, 2019 at 15:42
  • @DKBose your question was about a single or a few specific files as I understand it. Anything is possible of course, a directory, a set of files etc. As it is, it indeed is meant for a defined file, not a dir. Multiple instances can be called of course. Aug 29, 2019 at 15:44
  • In my question I have "I often edit a variety of plain text files such as .css, .mkd, .sh, and .txt as well as .odt and .ods files." So I do want a solution applicable for various files located anywhere in my home folder, in ~/Documents, ~/Dropbox, ~/.themes, etc. Of course, I won't be editing them all at the same time. Your solution is perfect if I'm editing (and saving) just the one file repeatedly in a session.
    – DK Bose
    Aug 29, 2019 at 15:50
  • 1
    Suggestion: make directory and put symlink to files you want to backup periodically. Jacob can edit his code to backup everything in that specific directory with no problems Aug 29, 2019 at 16:04
0

I came up with the following script, timestamp.sh, and assigned Ctrl+Super+T to run it.

#!/bin/bash

xgg="$(xdotool getactivewindow getwindowname)"

if [[ "$xgg" == *" - gedit" ]]

  then

    sleep 1 && xdotool key "ctrl+s"

  else

    sleep 0.3 && xdotool key "alt+f" && sleep 0.1 && xdotool key s

fi

nwt(){
    find ~/ \
    ! -path "$HOME/.mozilla/*" \
    ! -path "$HOME/.config/libreoffice/*" \
    ! -path "$HOME/Public/*" \
    ! -path "$HOME/.local/share/Trash/*" \
    ! -path "$HOME/.config/google-chrome/*" \
    \( -name "*ods" \
    -o -name "*txt" \
    -o -name "*html" \
    -o -name "*odt" \
    -o -name ".bashrc" \
    -o -name ".bash_aliases" \
    -o -name "*\.sh" \
    -o -name "*\.mkd" \) \
    -type f  -printf '%T@ %p\n' | sort -r | head -1 | cut -f2- -d ' '
}

s=$(nwt)

sleep 2 && cp $(nwt) $HOME/Public/"${s##*/}-$(date '+%Y%m%d%H%M%S')"
  • the script uses xdotool to simulate key presses and to detect the window title because gedit is different than the other editors in that it doesn't use the "legacy" Alt+F+S.

  • some of the code in nwt is from here.

  • the use of ${s##*/} is described here.

  • the sleeps may not be needed on a machine with good specs.

The resultant backup filename looks like this:

/home/dkb/Public/find-Carla-Schroder.mkd-20190829222116

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.