Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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?

share|improve this question

3 Answers 3

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 
done 

Explanation:

  • 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.
share|improve this answer
    
+1 very elegant! –  slm Mar 11 at 16:49

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"' _ {} +
share|improve this answer

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/~$//')
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.