Command Line Trash - Programming On Unix

Users browsing this thread: 1 Guest(s)
venam
Administrators
Hello nixers,
No this isn't a post trashing shell scripting.

Handling files on the command line is most of the time a non-reversable process, a dangerous one in some cases (Unix Horror Stories). There are tricks to avoid the unnecessary loss and help in recovering files if need be.

Quote:Users do not expect that anything they delete is permanently gone. Instead, they are used to a “Trash can” metaphor. A deleted document ends up in a “Trash can”, and stays there at least for some time — until the can is manually or automatically cleaned.

In this thread we'll list what ideas we have on this topic, novel or not so novel.



There's the usual aliasing of rm to mv into a specific directory, a trash can for the command line.
This can be combined with a cron job or timer that cleans files in this directory that are older than a certain time.

You can check the actual XDG trash documentation that goes into great details about what needs to be taken into consideration:
https://specifications.freedesktop.org/t...c-1.0.html

In $HOME/.local/share/Trash ($XDG_DATA_HOME/Trash) and usually at least split into two directories:
  • files for the actual exact copy of the files and directories (including their permission, and they should not override if two have the same names)
  • info, that contains the information about where and what name the deleted file had, in case it needs to be restored. And also the date it was deleted.

Another way to avoid loosing files is to keep backups of the file system. This can be done via a logical volume management be it included in the file system itself (ZFS, btrfs, etc..) or not.

So nixers, what's your command line trash, how do you deal with avoidable losses.
jkl
Long time nixers
+1 for file system backups: ZFS, HAMMER and (to some extent) Venti are fantastic.
Not everyone has FreeDesktop stuff.
z3bra
Grey Hair Nixers
I never understood ZFS well.. To me it's like the openstack of file systems. Huge lot of features, nearly too much to be understood by the lambda sysadmin :)

Venti is awesome. It's pifs but actually usable! The thing is that it won't magically backup your filesystem. If I understand correctly, It keeps the data, but you have to keep track of the file indexes yourself (kind of like any filesystem, when you unlink(2), the data remains on disk. Venti just NEVER overwrites it).

I like the idea of using the FS has a way to prevent data loss, but you'll either need to take snapshots very often, or need some kind of version control implemented IN the FS directly.
venam
Administrators
(08-03-2019, 04:35 AM)z3bra Wrote: I like the idea of using the FS has a way to prevent data loss, but you'll either need to take snapshots very often, or need some kind of version control implemented IN the FS directly.

Indeed, it might be overkill for a single file but is quite useful, acting as a backup, in the case when multiple files are lost.
z3bra
Grey Hair Nixers
Maybe we should consider a new undo(2) syscall, which would undo the latest syscall! Afyer all, recreating an inode shouldn't be too hard.
Would that be possible? Are there any syscall that cannot be reverted?
jkl
Long time nixers
kill(), probably.
phillbush
Long time nixers
These are the scripts I use to trash and "untrash" (remove from trash) files.
I set the $TRASH environment variable to $HOME/trash. I do not like the freedesktop convention on using .local/share

trash:
Code:
#!/bin/sh
# trash: move files to trash $TRASH
# this file in public domain

usage() {
    echo "usage: trash file..." >&2
    exit 1
}

# get trash directory to move file $1 into
gettrash() {
    mountfile="$(df "$1" | awk 'END {print $NF}')"
    mounthome="$(df "$HOME" | awk 'END {print $NF}')"

    if [ "$mountfile" != "$mounthome" ]
    then
        echo "$mountfile/.Trash-$UID"
    else
        echo ${TRASH:-$HOME/trash}
    fi

}

# delete file with full path $1 into trash directory $2
trash() {
    basename=$(basename "$1")
    body=${basename%.*}
    ext=${basename##$body}

    i=""
    while [ -e "$2/files/$body$i$ext" ]
    do
        if [ -z "$i" ]
        then
            i=1
        else
            i=$((i + 1))
        fi
    done

    cat > "$2/info/$body$i$ext.trashinfo" <<END
[Trash Info]
Path=$1
DeletionDate=$(date +"%FT%H:%M:%S")
END

    mv "$1" "$2/files/$body$i$ext"
}

[ $# -eq 0 ] && usage

for f in "$@"; do

    [ ! -e $f ] && echo "trash: $f: no such file or directory" >&2 && exit 1

    trashdir="$(gettrash "$f")"
    filename="$(readlink -f "$f")"

    # create trash directory
    if ! mkdir -p "$trashdir/files" "$trashdir/info"
    then
        echo "trash: unable to create $trashdir"
        exit 1
    elif [ "$(ls -ld "$trashdir" | awk 'END {print $1}')" != 'drwx------' ]
    then
        chmod 700 "$trashdir"
    fi

    trash "$filename" "$trashdir"
done

exit 0

untrash:
Code:
#!/bin/sh
# untrash: remove files from trash
# this file in public domain

usage() {
    echo "usage: untrash file..." >&2
    exit 1
}

# get info file of deleted file $2 in path $1
getinfo() {
    echo "$1/../info/$2.trashinfo"
}

# get original name of deleted file $2 on info file $1
getorigfile() {
    echo "$(cat $1 | awk -F= '/Path=/ {print $NF}')"
}

[ $# -eq 0 ] && usage

for f in "$@"; do
    if [ ! -e "$f" ]
    then
        echo "trash: $f: no such file or directory" >&2
        exit 1
    fi

    filename="$(readlink -f "$f")"
    basename="$(basename "$f")"

    # get directory of deleted files
    filesdir="$(dirname "$filename")"
    if [ "$(basename $filesdir)" != "files" ]
    then
        echo "trash: $basename: file is not in trash" >&2
        exit 1
    fi

    # get file containing information on deleted file
    infofile="$(getinfo "$filesdir" "$basename")"
    if [ ! -f "$infofile" ]
    then
        echo "trash: $basename: cannot find original directory" >&2
        exit 1
    fi

    # get original name of deleted file
    origfile="$(getorigfile "$infofile" "$basename")"
    if [ -e "$origfile" ]
    then
        echo "trash: $basename: file already exists on original directory" >&2
        exit 1
    fi

    # move deleted file to its original place
    if ! mv "$filename" "$origfile"
    then
        echo "trash: $basename: cannot move file to its original directory" >&2
        exit 1
    fi

    # remove deleted file's information file
    if ! rm "$infofile"
    then
        echo "trash: $basename: cannot clean file from trash" >&2
        exit 1
    fi
done

exit 0