13

Ideally I'd like a command like this

rm --only-if-symlink link-to-file

because I have burned myself too many times accidentally deleting the file instead of the symlink pointing to the file. This can be especially bad when sudo is involved. Now I do of course do a ls -al to make sure it's really a symlink and such but that's vulnerable to operator error (similarly named file, typo, etc) and race conditions (if somebody wanted me to delete a file for some reason). Is there some way to check if a file is a symlink and only delete it if it is in one command?

1
  • 1
    I don't think that the race condition should be a concern. Anybody who would be able to remove the symlink and replace it with a file would need write permission on the directory, which means they could also delete the file themselves. Mar 21, 2016 at 3:17

3 Answers 3

21
 $ rm_if_link(){ [ ! -L "$1" ] || rm -v "$1"; }

 #test
 $ touch nonlink; ln -s link
 $ rm_if_link nonlink
 $ rm_if_link link
   removed 'link'     
8
  • 5
    Surely it would be more clear to remove ! and change || to && Mar 20, 2016 at 22:34
  • 6
    @glennjackman I've made it a habit to prefer the || form. && brings things down if you're in set -e mode. Mar 20, 2016 at 22:44
  • @PSkocik That's risky in general since it doesn't distinguish between the interesting case (the file exists and is a symlink) and other reasons for a non-zero exit code, such as an empty unquoted value ([ ! -L $1 ]), using an invalid operator ([ ! -l foo ]), or even syntax error ([ ! -q foo || echo foo)
    – l0b0
    Mar 21, 2016 at 8:32
  • 2
    @XiongChiamiov The problem with rm_if_link(){ [ -L "$1" ] && rm -v "$1"; } is that running it on a nonlink in set -e mode will kill your shell process. You could append || : but then it becomes, IMHO, even less clearer than the ! || version, and it will prevent a rm failure from killing you in set -e, which is likely undesirable. ! || is quite concise and clear, and doesn't suffer from any of these problems. Mar 21, 2016 at 17:18
  • 1
    Nope. Other options: a) put the rm inside your test, b) use an actual if statement, c) don't go around using -e inside interactive shells (and test your scripts!). If you want to argue about readability, using if to test if something is a link is pretty clearly the winner. Mar 23, 2016 at 20:19
7

You can use find and its -type l test condition (which tests to see if the object found is a link or not)

For example, if you have a file called foo in the current directory, you could do this:

$ find . -type l -iname "foo" -delete

You might be able to simplify that with just:

$ find . -maxdepth 1 -type l -delete

Which would delete all symlinks in the current directory.

Warning:

The -delete option in find is really really dangerous. BE SURE TO PLACE IT AT THE END OF THE FIND COMMAND. If you misplace it, it will delete everything it finds regardless of whether the results match your conditional.

As suggested in the comments, a safer option might be to use find and rm -i (which forces you to confirm file removal) in conjuction:

$ $ find . -type l -iname "foo" | xargs rm -i

Personally I use -exec trash {} \; to temporarily delete files, because I, like yourself, have been burned by rm in the past. Double goes for a misplaced -delete flag.

http://slackermedia.ml/trashy

7
  • 1
    Instead of -delete you can just print it and pass it to xargs: find . -type l -iname "foo" | xargs rm -i it's safer.
    – Boban P.
    Mar 20, 2016 at 21:34
  • 1
    I like@BobanP.'s use of rm -i for safety, for sure. Mar 20, 2016 at 21:35
  • 3
    "This would delete all symlinks in the current directory" ... and any point below it.
    – Joseph R.
    Mar 20, 2016 at 21:36
  • 1
    As @JosephR. said, it will go all the way down the road. Add -maxdepth 1 in find.
    – Boban P.
    Mar 20, 2016 at 21:41
  • 1
    Still breaks on filenames with spaces. Suggest using -print0 for find and -0 for xargs.
    – Wildcard
    Mar 20, 2016 at 21:44
5
zsh -c 'rm foo(@)'

@ is a glob qualifier; the pattern foo(@) matches what foo matches, but only the symbolic links.

foo(-@) would only match broken symbolic links. foo(@,L0) would match only symbolic links and empty files.

Of course, if you're running zsh in the first place, you just need to type rm foo(@). You do need to take care not to press Enter before typing (@).

1
  • 1
    Zsh sounds more and more powerful when I see answers like this.
    – Jeff Schaller
    Mar 20, 2016 at 22:00

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .