bashgitterminalrmln

How to make rm not delete symbolic links?


So, I was getting rid of the unwanted files and I deleted some folder. After some time I saw another folder which appears red in ls -la. I knew I deleted the actual folder and thus I was very sad and tried to run foremost to recover but bad luck. Can anyone give me some good aliases/function that deletes like rm -rf but if that file is linked to other files then it won't delete. Like if /usr/bin/a.py is linked to /root/a.py and I run rm -rf /usr/bin/a.py then /root/a.py will be deleted. How can I prevent that from an alias?


Solution

  • Joe W's answer is correct in saying rm typically does not delete the targets of symlinks, but to say that it "does not follow symlinks" is not quite accurate, at least on my system (GNU coreutils 8.25). And deleting files is a place where accuracy is pretty important! Let's take a look at how it behaves in a few situations.

    If your symlink is to a file, rather than to a directory, there is no plausible way to accidentally delete the target using rm. You would have to do something very explicit like rm "$(readlink file)".

    Symlinks to directories, however, get a bit dicey, as you saw when you accidentally deleted one. Here's a test case we can use:

    $ mkdir test1
    $ touch test1/foo.txt
    $ ln -s test1 test2
    $ ls -lR
    .:
    total 4
    drwxrwxr-x 2 soren soren 4096 Jun 29 17:02 test1
    lrwxrwxrwx 1 soren soren    5 Jun 29 17:02 test2 -> test1
    
    ./test1:
    total 0
    -rw-rw-r-- 1 soren soren 0 Jun 29 17:02 foo.txt
    

    These are all safe:

    These are not safe:

    The last unsafe case is probably obvious behavior, at least to someone well-acquainted with the shell, but the two before it are quite a bit more subtle and dangerous, especially since tab-completing the name of test2 will drop the trailing slash in for you!

    It's interesting to note that test has similar behavior, considering a symlink to a directory with a trailing slash to be not a symlink but a directory, while a symlink without a trailing slash is both:

    $ [ -L "test2" ] && echo "is link"
    is link
    $ [ -d "test2" ] && echo "is directory"
    is directory
    $ [ -L "test2/" ] && echo "is link"
    $ [ -d "test2/" ] && echo "is directory"
    is directory
    

    Here's a previous treatment of "deleting a symlink to a directory without deleting the target," with a less thorough analysis of exactly what works and what doesn't but with a bunch of other useful information.


    Unfortunately, I am not aware of any way to alias rm to prevent this mistake. I suppose you could write a function to parse the arguments to rm and warn you if any of them are symlinks that end with a trailing slash, something like this:

    function rm {
        for i in "$@"; do
            if [[ $i =~ /$ ]] && [ -L "${i:0:-1}" ]; then
                read -rp "Really delete symlink '$i' with trailing slash (y/n)? " result
                [ "$result" != "y" ] && return
            fi
        done
        command rm "$@"
    }
    

    Use at your own risk, though! It passes shellcheck and it worked when I tested it, but implementing a wrapper on top of something as fundamental and dangerous as rm gives me the heebie-jeebies.

    Two other potentially useful switches you might include in your alias/function would be --one-file-system (at least skips files past a symlink or mount onto a different drive) and, if you don't already use it, -i or -I to prompt when doing something potentially dangerous.