76

Is there a Linux script / application which, instead of deleting files, moves them to a special “trash” location? I’d like this as a replacement for rm (maybe even aliasing the latter; there are pros and cons for that).

By “trash” I mean a special folder. A single mv "$@" ~/.trash is a first step, but ideally this should also handle trashing several files of the same name without overwriting older trashed files, and allow to restore files to their original location with a simple command (a kind of “undo”). Furthermore, it’d be nice if the trash was automatically emptied on reboot (or a similar mechanism to prevent endless growth).

Partial solutions for this exist, but the “restore” action in particular isn’t trivial. Are there any existing solutions for this which don’t rely on a trash system from a graphical shell?

(As an aside, there have been endless discussions whether this approach is justified, rather than using frequent backups and VCS. While those discussions have a point, I believe there’s still a niche for my request.)

  • 7
    This may be related to the SuperUser question Two commands to move files to trash. What's the difference?. I've used gvfs-trash in the past, but never had a need to restore from the command-line until you sparked my curiosity. The answer to the linked question may be of help. – ephsmith Jul 10 '12 at 23:02
  • 1
    @ephsmith Thanks, good link. The problem with those approaches though is that they are bound to specific desktop shell (what’s the correct term here?) implementations, something which I want to avoid. – Konrad Rudolph Jul 11 '12 at 09:32
  • 2
    Is moving files from any filesystem to your ~ intentional? Because some day you might be deleting a 4GB iso image residing on a dir mounted with sshfs from a really remote server. – Mischa Arefiev Jul 11 '12 at 11:40
  • 5
    Do whatever you want such as the solutions outlined in the answers below, but don't name it rm. As pointed out by others, renaming/repurposing standard commands leaves you vulnerable when you habitually try to use them on other systems, but it also will cause problems for anyone else (perhaps assisting you) using your system/account when unexpected results occur. – Joe Jul 14 '12 at 20:43
  • https://github.com/nateshmbhat/rm-trash . Checkout this Linux utility works perfectly and handles all situations. – Natesh bhat Dec 16 '18 at 02:25
  • A solution without third-party tools is simply with the mv command, e.g. mv test .local/share/Trash/files. If you take this habit, you will avoid accidental removal of files. For example, this would have prevented me from removing accidentally a part of my home folder because I wanted to remove a folder named ~ created by mistake... I think this solution is safer than tweaking the rm command because one day, you may use the rm command on a system with the default rm command. The mv -i alias will alert before you overwrite existing files. – baptx May 01 '20 at 15:48
  • 1
    Some perspective, 10 years after asking this question: I never ended up using any of the solutions. I just continued using rm and, yes, I occasionally delete the wrong files by accident. Thanks to automatic backups this usually hasn’t been an issue (there are rare exceptions when working on scratch space, but by definition these files aren’t important … at least, ideally). – Konrad Rudolph Aug 19 '22 at 06:55
  • 1
    The best answer to this comes from @Ehsan88. https://unix.stackexchange.com/a/710550/280224 – ejkitchen Nov 01 '22 at 16:04

12 Answers12

53

There is a specification (draft) for Trash on freedesktop.org. It is apparently what is usually implemented by desktop environments.

A commandline implementation would be trash-cli. Without having had a closer look, it seems to provide the funtionality you want. If not, tell us in how far this is only a partial solution.

As far as using any program as replacement/alias for rm is concerned, there are good reasons not to do that. Most important for me are:

  • The program would need to understand/handle all of rm's options and act accordingly
  • It has the risk of getting used to the semantics of your "new rm" and performing commands with fatal consequences when working on other people's systems
zpea
  • 678
  • There is also libtrash which moves all deleted files automatically to the trash via LD_PRELOAD (but it seems to have several bugs). Autotrash helps to clean the trash in an easy way. – jofel Jul 11 '12 at 08:47
  • 1
    I’m wondering about the getting-in-the-habit-of-using-rm thingy. I’m already in the habit, unfortunately. – Konrad Rudolph Jul 11 '12 at 09:43
  • @jofel: libtrash has a really nice concept. A few layers deeper than the other approaches. It's a pity it is buggy (and does not seem very active). – zpea Jul 11 '12 at 10:29
  • 4
    @KonradRudolph: I meant that one gets used to the fact that rm (the replaced one) does not really delete anything, so that one is less careful, as a restore is always possible. Of course, using rm itself is not a bad thing, nor is getting used to it. – zpea Jul 11 '12 at 10:33
  • 5
    I’ve ended up using this solution, and disabling rm so I can’t use it accidentally (there’s still /bin/rm in case I really need it). – Konrad Rudolph Sep 05 '12 at 08:16
  • 1
    @KonradRudolph is it safe to disable rm? I think some scripts relying on it could fail. – baptx May 01 '20 at 16:01
  • @baptx If you disable rm with an alias then scripts see the original rm. That said, it’s been eight years since I did this. I haven’t used this solution since, I now use ZFS or similar solutions to have continuous, automatic backups. – Konrad Rudolph May 01 '20 at 22:22
31

The previous answers mention commands trash-cli and rmtrash. Neither of those are found by default on Ubuntu 18.04, but the command gio is. Commanding gio help trash outputs:

Usage:
  gio trash [OPTION…] [LOCATION...]

Move files or directories to the trash.

Options: -f, --force Ignore nonexistent files, never prompt --empty Empty the trash

I tested using gio trash FILENAME on the command line, and it works just like I'd selected the file in the file browser and clicked the DEL button: the file is moved to the desktop's Trash folder. (The command doesn't prompt for confirmation even though I did not use the -f option.)

Deleting files this way is reversible, while being more convenient than redefining rm to be rm -i for safety and having to confirm each deletion, which still leaves you out of luck if you accidentally confirm a deletion you shouldn't have.

I added alias tt='gio trash' to my alias definitions file; tt is a mnemonic for "to trash".

Added on edit on 2018-06-27: On server machines, there is no equivalent of a trash directory. I've written the following Bash script that does the job; on desktop machines, it uses gio trash, and on other machines, moves the file(s) given as parameter(s) to a trash directory it creates. The script is tested to work; I use it all the time myself. Script updated on 2024-03-24.

#!/bin/bash

move_to_trash

Teemu Leisti 2024-03-24

USAGE:

move_to_trash moves the file(s) given as argument(s) to the trash directory,

if they are not already there. It works both on (Gnome) desktop and server

hosts.

RATIONALE:

move_to_trash is intended as a command-line equivalent of deleting a file or

directory from a graphical file manager, which, on Gnome desktop hosts,

moves the deleted file(s) to a built-in trash directory.

On a server host, the script uses a custom trash directory (~/.Trash/), and

the analogy of moving a file to trash is not perfect, as the script does not

offer the functionality of restoring a trashed file to its original

location, nor that of emptying the trash directory. Rather, it offers an

alternative to the 'rm' command, thereby giving the user the peace of mind

that they can still undo an unintended deletion before emptying the custom

trash directory.

On a server host, the user will have to perform an undo by commanding 'mv'

on the moved file, and to empty the custom trash directory by commanding:

rm -rf ~/.Trash/* ~/.Trash/.*

IMPLEMENTATION:

To determine whether it's running on a desktop host, move_to_trash tests for

the existence of the gio command and of directory ~/.local/share/Trash. (The

'gio' command only exists for Gnome.) In case both exist, the script relies

on calling 'gio trash'. Otherwise, it treats the host as a server.

On server hosts, there is no built-in trash directory, so move_to_trash

creates directory ~/.Trash/, unless it already exists. (The script errors

out if there is an existing file ~/.Trash.)

move_to_trash appends a millisecond-resolution timestamp to all the files it

moves to the trash directory, to inform the user of the time of the deletion

and to avoid overwrites.

move_to_trash will not choke on a nonexistent file. It outputs the final

disposition of each filename argument: does not exist, was already in trash,

or was moved to trash.

COPYRIGHT WAIVER:

The author dedicates this Bash script to the public domain by waiving all of

their rights to the work worldwide under copyright law, including all

related and neighboring rights, to the extent allowed by law. You can copy,

modify, distribute, and perform the script, even for commercial purposes,

all without asking for permission.

is_desktop=0 gnome_trash_directory=$(realpath ~/.local/share/Trash/)

gio_command_exists=0 if command -v gio > /dev/null 2>&1 ; then gio_command_exists=1 fi

if [[ -d "$gnome_trash_directory" ]] && (( gio_command_exists == 1 )) ; then # Executing on a Gnome desktop. is_desktop=1 trash_dir_abspath="$gnome_trash_directory" else # Not executing on a Gnome desktop, so attempt to use a custom trash # directory. trash_dir_abspath=$(realpath ~/.Trash) if [[ -e "$trash_dir_abspath" ]] ; then if [[ ! -d "$trash_dir_abspath" ]] ; then echo "Error: $trash_dir_abspath exists, but is not a directory." exit 1 fi else mkdir "$trash_dir_abspath" echo "Created directory $trash_dir_abspath" fi fi

Deal with all filenames given as arguments. (The concept "filename" covers

both file and directory names here.)

for file in "$@" ; do file_current_abspath=$(realpath -- "$file") file_basename=$(basename -- "$file_current_abspath") if [[ ! -e $file_current_abspath ]] ; then echo "does not exist: $file_current_abspath" elif [[ "$file_current_abspath" == "$trash_dir_abspath"* ]] ; then echo "already in trash: $file_current_abspath" else # $file_current_abspath exists and is not yet in the trash directory, so # move it there. if (( is_desktop == 1 )) ; then # We're executing on a Gnome desktop, so just use gio. gio trash "$file_current_abspath" else # We're not executing on a Gnome desktop, so move the file to the # custom trash directory, with a new name that appends a # millisecond-resolution timestamp to the original. name_head="$trash_dir_abspath/$file_basename"DELETED_ON file_new_abspath="$name_head$(date '+%Y-%m-%d_AT_%H-%M-%S.%3N')" while [[ -e "$file_new_abspath" ]] ; do # Generate a new name with a new timestamp, as the previously # generated one denoted an existing file or directory. It's # quite unlikely we'll have to execute this even once. file_new_abspath="$name_head$(date '+%Y-%m-%d_AT_%H-%M-%S.%3N')" done # There is no file or directory named $file_new_abspath, so we can # use it as the move target. /bin/mv "$file_current_abspath" "$file_new_abspath" fi echo "moved to trash: $file_current_abspath" fi done

Teemu Leisti
  • 505
  • 5
  • 7
13

Trash-cli is a linux application that can be installed using apt-get in Ubuntu or yum in Fedora. Using the command trash listOfFiles will move the specified into your trash bin.

namu
  • 241
  • 2
  • 5
6

It's a bad idea. Change the habit instead. Here's how:

See this answer in the Apple community: https://apple.stackexchange.com/questions/17622/how-can-i-make-rm-move-files-to-the-trash-can

The answer basically says it's a bad idea because it does not change the bad habit of using rm. To change that habit use the following. I adjusted it for linux bash:

  • Add this line to ~/.bashrc:

      alias rm="echo Use the full path i.e. '/bin/rm', consider using trash"
    
  • Install trash-cli with apt install trash-cli and use trash command from now on.

This causes your habit to do rm to go away. And every time you enter rm it shows the statement above. It's good to note that trash can also move the directory.

Ehsan88
  • 498
  • Given all of the caveats of aliasing rm, this is the best answer. Stop using rm directly except only when you really want to – ejkitchen Nov 01 '22 at 15:58
5

Start by defining a move_to_trash function:

move_to_trash () {
    mv "$@" ~/.trash
}

Then alias rm to that:

alias rm='move_to_trash'

You can always call old rm by escaping it with a backslash, like this: \rm.

I don't know how to make the trash directory empty on reboot (depending on your system, you may have to look into the rc* scripts), but it could also be worthwhile to create a cron task that empties the directory periodically.

rahmu
  • 20,023
  • 2
    Unfortunately, that was the easy part … :/ – Konrad Rudolph Jul 10 '12 at 22:11
  • This script could also create a text file in a hidden directory for each file which contains the directory it was in. A restore script could read the old location and move it back. – ephsmith Jul 10 '12 at 22:20
  • 1
    This also has a hazard of multiple deleted files with the same name would collide in the trash directory, and only the last one "deleted" would survive to be able to be recovered. – killermist Jul 10 '12 at 23:05
  • @killermist, yes. Of course one would need to do something additional with the move command. Name the "trashed" file whatever you want and keep the original path :| This all screams "why re-create the wheel". There are existing solutions to this problem. – ephsmith Jul 11 '12 at 00:47
  • Also, use a different alias name. Work on another machine without your aliases, one call to rm and there go your files. del might be a better choice. – glenn jackman Jul 11 '12 at 00:51
  • 1
    it overwrite existing file with same name – daisy Jul 11 '12 at 01:22
  • Others already pointed out it doesn't work if you have the same filenames multiple times, but you also loose the path, so the recovery is not obvious. – redseven Jul 14 '22 at 14:07
4

There's a little utility called rmtrash which does this.

It doesn't seem to respond to params like -r or -f (it appears to essentially just be moving the file/directory to the ~/.Trash directory), but it won't override files with the same name (it appends "Copy" to like-named files/directories).

To install with brew

brew install rmtrash
alias rm='rmtrash' >> ~/.bashrc
  • https://github.com/nateshmbhat/rm-trash . "rm-trash" , handles duplicate file names and recursive deletions too. Check it out. – Natesh bhat Dec 16 '18 at 02:26
4

Here's a quick and dirty trash system that copes with name clashes and even allows multiple deleted files on the same path as long as you don't delete more than one file per second.

Warning: I typed this code directly into my browser. It's probably broken. Don't use it on production data.

trash_root=~/.trash
mkdir "$trash_root"
newline='
'
trash () (
  time=$(date +%Y%m%d%H%M%S)
  for path; do
    case $path in /*) :;; *) path=$PWD/$path;; esac
    mkdir "$trash_root${path%/*}"
    case ${path##*/} in
      ?*.*) ext="${path##*.}"; ext="${ext##*$newline}";;
      *) ext="";;
    esac
    metadata="Data: $hash.$ext
Date: $time
Path: $path
"
    hash=$(printf %s "$metadata" | sha1sum)
    printf %s "$metadata" "$trash_root/$hash-$time-metadata"
    mv "$path" "$trash_root/$hash.$ext"
  done
)

untrash () (
  IFS='
  '
  root=$PWD
  cd "$trash_root" || return 2
  err=0
  for path; do
    if [ -e "$path" ]; then
      echo 1>&2 "Not even attempting to untrash $path over an existing file"
      if [ $err -gt 2 ]; then err=2; fi
      continue
    fi
    case $path in /*) :;; *) path=$root/$path;; esac 
    if metadata=$(grep -l -F -x "Path: $path" *-metadata |
                  sort -t - -k 2 | tail -n 1); then
      mv "${metadata%%-*}".* "$path"
    else
      echo 1>&2 "$path: no such deleted file"
      if [ $err -gt 1 ]; then err=1; fi
    fi
  done
  return $err
)

Known issues:

  • Doesn't cope gracefully if you try to delete the same file several times concurrently.
  • The trash directory may become huge, the files should be dispatched into subdirectories based on the first few digits of the hash.
  • trash should cope with newlines in file names, but untrash doesn't because it relies on grep and the newlines are not escaped in the metadata file.
2

You can use my del:

http://fex.belwue.de/fstools/del.html

del moves files in a .del/ subdirectory (and back)

usage: del [-v] [-u] file(s)
       del [-v] -p [-r] [-d days] [directory]
       del [-v] -l
options: -v   verbose mode
         -u   undelete file(s)
         -p   purge deleted files [older than -d days]
         -r   recursive (all subdirectories)
         -l   list deleted files
examples: del *.tmp         # delete all *.tmp files
          del -u project.pl # undelete project.pl
          del -vprd 2       # verbose purge deleted files older than 2 days
Framstag
  • 169
1

I am testing this very simple alternative, which creates a date versioned ~/.trash for me.

I belive it doesn't have the (really valid) problems mentioned by the accepted answer:

The program would need to understand/handle all of rm's options and act accordingly

You simply use rm something. Doesn't need to pass any flags. If you do, say, run rm -rf, it will ignore the r, as mv doesn't support it and the f won't really make a difference, as the the ~/.trash is date versioned.

It has the risk of getting used to the semantics of your "new rm" and performing commands with fatal consequences when working on other people's systems

Like I said, the new semantics would end up being me running rm something. If I go back to using this in other machines, it will only work for files. For folders it will give the usual "you are missing -r" type of error.

move_to_trash() {
  local trash_dir
  trash_dir="${HOME}/.trash"

  if [ ! -d "${trash_dir}" ]; then
    echo "Trash directory doesn't exist. Creating..."
    mkdir "${trash_dir}"
    echo "Created!"
  fi

  local new_dir_name
  new_dir_name="${trash_dir}/$(date +%Y-%m-%d_%H-%M-%S)/"
  mkdir "${new_dir_name}"

  echo "Moving to: ${new_dir_name}"
  mv "$@" ${new_dir_name}
  echo 'Done!'
}

alias really-rm="/bin/rm"
alias rm="move_to_trash"
1

As stated by Ehsan88 it is a bad Idea to alias rm because it makes a habit of expecting rm to move the file to trash.
Put the following in .bashrc or the equivalent bash configuration file:
You should alias rm to send a notice message by adding the following:

alias rm='echo "rm is disabled, use trash"'

Then alias whatever command you want to move to Trash:

alias trash='mv -i -t ~/.local/share/Trash/files/ $@'

We pass in -i to alert if a file of the same name has already been deleted. We pass in -t to declare the destination. $@ is used to pass in the arguments.

0

In KDE 4.14.8 I used the following command to move files to trash (as if it were removed in Dolphin):

kioclient move path_to_file_or_directory_to_be_removed trash:/

Appendix I: I found about the command with

    ktrash --help
...
    Note: to move files to the trash, do not use ktrash, but "kioclient move 'url' trash:/"

Appendix II: the function (then source it in your .bashrc)

function Rm {
    if [[ "$1" == '--help' ]] ; then
        echo 'USAGE:'
        echo 'Rm --help # - show help'
        echo 'Rm file1 file2 file3 ...'
        echo 'Works for files and directories'
        return
    fi
    for i in "$@" ;
    do
        kioclient move $i trash:/ && echo "$i was trashed"
    done
}
0

A simple and reliable one.

Add this to .bashrc (or similar)

This date is added to avoid the file with same name being overwritten

function rm {
    local trash=~/.trash
    mkdir -p $trash
    for file in "$@"; do
        local timestamp=$(date +"%Y-%m-%d_%H-%M-%S")
        local new_name="${file%.*}_${timestamp}.${file##*.}"
        mv -i "$file" "$trash/$new_name"
    done
}