I accidentally deleted some files spread across my home directory, but I do not know exactly which ones were removed. How can I get a list of all backup files missing their corresponding file? (equivalently, files having names ending with a tilde, without there being another file in the same directory with the same name sans trailing tilde?)

I tried a few things so far; although I don't remember the exact flags, it was something like:

grep -Rlv '(.*)\n\\1~|.*(?!~)'

That didn't work, and neither did:

ls -R | grep -v '(.*)\n\\1~|.*(?!~)'

How can I find these files?


Just find all files with a tilde, remove the tilde and look for the "original":

find . -name '*~' -print0 | while IFS= read -r -d $'\0' file; do 
  [ -e "${file%\~}" ] ||  echo cp "$file" "${file%\~}"; done 


  • find ~/ -name '*~' -print0 : find all files in $HOME that end in a tilde and print them with the null (\0) character. The last is necessary to deal with weird file names that contain newlines etc.

  • while IFS= read -r -d $'\0' file; : read each file found by find into $file`.

    • IFS= : turns of bash's automatic split at whitespace
    • -r : treat backslashes literally (not as escape characters)
    • -d $'\0' : sets the input field delimiter to the null character.
  • "${file%\~}" : removes the tilde, see here
  • [ -e "${file%\~}" ] || echo 'cp '$file' '${file%\~}" : the echo will be run only if the file name (sans tilde) does not exist. To actually copy the files, just remove the echo.

In zsh, you can use a glob qualifier to filter matches. The e modifier lets you specify arbitrary code; it's easier on the parsing to write a function and call it with the + modifier.

zsh -c 'deleted () { ! [ -e ${REPLY%\~} ] }; ls -ld **/*~(+deleted)'

If zsh isn't available, you can use find.

find ~ -name '*~' -type f -exec sh -c '[ -e "${0%\~}" ] && ls -ld "$0"' \;

or, faster:

find ~ -name '*~' -type f -exec sh -c 'for x; do [ -e "${x%\~}" ] && ls -ld "$x"' _ {} +

An alternative method to using find. Cache both lists of files (stripping "~") and diff the two anonymous named pipes storing each set.

diff <(ls -R *[^~] | xargs -n 1 readlink -f)  <(ls -R *[~] | xargs -n 1 readlink -f | sed 's/~$//')

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.