0

I do and get a list of files where I would like to delete many duplicate backup files

find . -type f -name '._*' 

I would like to find those files which have a corresponding filename

  • /home/masi/._test.tex matches /home/masi/test.tex
  • /home/masi/math/lorem.png matches /home/masi/math/._lorem.png

Pseudocode about files wanted to be saved filename which has corresponding ._filename but also save filename without ._filename

find . -type f -name '._*' -exec \ 
   find filenameWithoutDotUnderscore, if yes, print the filename

Pseudocode 2 clarification about files wanted to be removed = ._filename if there is a corresponding filename

  • If there is filename and ._filename in the same directory, print ._filename such that I can remove the duplicate = ._filename.
  • Exclude filenamePart1_.filenamePart2, bok_3A.pdf, ... in ._filename.
  • Do not remove ._filename if there is no corresponding filename in the same directory.

Reviewing Wildcard's command

I do find . -type f -name '._*' -exec sh -c 'for a; do f="${a%/*}/${a##*/._}"; [ -e "$f" ] && printf "rm -- %s\n" "$a"; done' find-sh {} + but it returns too many files. I think I need more && conditions beside the existence check ([ -e "$f" ]). It would be great to get here some content comparison and lastly diff if suspicion of much difference.

Systems: Ubuntu 16.04 and Debian 8.25
Bash: 4.3.42
Find: 4.7.0

3

You can do this with find, but to do it robustly you will need to embed a shell one-liner as well. The proper way to do this is one of the following:

Stuff the looping into the spawned shell:

find . -type f -name '._*' -exec sh -c 'for a in "$@"; do f="${a%/*}/${a##*/._}"; [ -e "$f" ] && printf %s\\n "$f"; done' find-sh {} +

Or, spawn a separate shell for each file to be tested (less efficient, potentially more readable):

find . -type f -name '._*' -exec sh -c 'f="${1%/*}/${1##*/._}"; [ -e "$f" ] && printf %s\\n "$f"' find-sh {} \;

To directly remove the backup files, change this to the following for a dry run:

find . -type f -name '._*' -exec sh -c 'for a; do f="${a%/*}/${a##*/._}"; [ -e "$f" ] && printf "rm -- %s\n" "$a"; done' find-sh {} +

Then once you're satisfied with the list of commands that gets printed, use:

find . -type f -name '._*' -exec sh -c 'for a; do f="${a%/*}/${a##*/._}"; [ -e "$f" ] && rm -- "$a"; done' find-sh {} +

Notes:

In all of these, the find-sh argument is an arbitrary string; you could put anything there. It gets set as $0 within the spawned shell and is used for error reporting.

for a in "$@"; do is exactly equivalent to for a; do.

printf is better than echo.

Quoting is important.

  • 1
    @Masi, the first two commands find the files whose names start in ._ and if there is a file in the same directory without the ._, print that file's name. They are system independent. – Wildcard Jun 10 '16 at 17:33
  • 1
    ${a%/*} expands to the value of the variable a with the last slash and everything after it removed; ${a##*/._} expands to the value of the variable a with everything up to the last occurrence of /._ removed. The / in between is a literal slash. See LESS='+/Parameter Expansion' man bash. – Wildcard Jun 10 '16 at 17:37
  • @Masi, do you want to remove the ._filename files or the filename files? I've bolded the relevant lines of my answer; I already included a "dry run" version.... – Wildcard Jun 10 '16 at 21:09
  • 1
    @Masi, dry run definition. I already provided that. – Wildcard Jun 10 '16 at 21:13
  • @Masi, [ -e "$f" ] is an existence check. Make a backup first. (You should have backups anyway.) Use the command in subdirectories first. Delete them manually, if you like. But your original exact question has been exactly answered. Certainly you should study up enough to understand what the command is doing before you blindly run it, but have you even attempted to understand what the command I posted does? – Wildcard Jun 21 '16 at 17:31
0

I made an a.sh (because I don't know how to do this as a function) which is just one line:

test -f "${*/._/}" && echo "$* matches ${*/._/}";

That is, I'm testing for the existence of a file whose name is all the arguments passed into the shell script, with the first instance of ._ removed (technically a search-and-replace with nothing in the 'replace' field). When it finds such a file it echoes the name of the two matching files.

Then I ran it with find ... -exec ./a.sh '{}' \;

About search (and replace) in bash: ${foo/bar/baz} means search the variable foo for the string bar and replace it with baz. You can try this experiment: x=abcdef; echo ${x/c/C}. So the code above is searching for ._ and replacing it with nothing. For more details, in man bash search for Pattern substitution.

  • 1
    This assumes that ._ doesn't appear anywhere in the full path other than the expected position. There is no reason to make that assumption—i.e. it is unjustified, and it's easy to write code that works without that assumption. You should at least mention the assumptions you are making. – Wildcard Jun 9 '16 at 21:59

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.