3

I have accidentally imported images from an app twice, resulting in list which repeats this pattern:

filename1.ext
filename1 2.ext
filename2.ext
filename2 2.ext

about 5,000 times.

All details (creation date, mod date, etc) match, except for the " 2" right before the extension.

I can delete these manually, but I don't think anyone would consider this a likeable task.

Can anyone please recommend a method – perhaps a Terminal code – which would delete all off the " 2" items in one fell swoop?

Note that the items and their duplicates are restricted to a single folder.

There's an obvious pattern here (" 2.ext") but I have no idea how to use it.

0

5 Answers 5

13

As much as I love the shell, it’s unforgiving with wildcards and spaces in general and worse if your script gets to folders you didn’t expect. Your case of all within one folder and no recursion limits your risk to script this cleanly.

To prepare, empty trash (so you can easily see / reverse this if needed) and consider making a backup - great advice any time you are about to automate destruction of thousands of files right next to ones you hope to retain.

In finder, search for space 2.ext with quotes. Select search in your folder to ensure you’re not searching everywhere and count the results.

“ 2.ext”

  • Command + A
  • Command + Delete
17

nohillside's answer to use rm *\ 2.ext will work just fine, but if you want extra confidence it's doing the right thing, you can instead use

mkdir to_be_deleted
mv -- *\ 2.ext to_be_deleted

That'll put all the files you want deleted in a separate folder - you can then use finder (or whatever) to check which files are still in the original folder, and which ones have been moved. When you're happy it looks good, delete the to_be_deleted folder.

Thanks to fd0 for suggesting the initial -- argument.

1
  • 7
    mv -- *\ 2.ext to_be_deleted would be safer. +1 for your answer anyways
    – fd0
    Commented Oct 3, 2022 at 13:08
9

In Terminal, rm *\ 2.ext should work.

PS: To be sure that the correct files get deleted, run ls *\ 2.ext first, or use rm -i *\ 2.ext to be prompted for each file.

2
  • 3
    As a paranoid person, I would first enter that command with "ls" instead of "rm". When I was 10% confident it was doing what I wanted, I'd edit the "ls" to "rm". Commented Oct 3, 2022 at 8:01
  • Using ls to test is awesome. Counting the files is useful to test ls *\ 2.ext | wc and ls | wc as well in cases like these.
    – bmike
    Commented Oct 3, 2022 at 9:49
0

As noted by others, there might be issues here with shell scripting depending on how the duplicates were made. You could theoretically avoid those by copying the folder and trying it first, but the only way to totally verify is if you went through them by hand which somewhat defeats the purpose.

This is a Python file I use for similar deduplication that I might have. It scans the folder for files by using their sha256, retains the highest file name alphabetically (which should generally keep the cleaner file names) and deletes the others. You can change the dry_run variable on the 3rd to last line to true like this:

if __name__ == '__main__':
d = Deduplicator(path, dry_run=True)
d.deduplicate()

To verify that the files you want to delete are actually what are going to be deleted. And, of course, change line 4 from /path/to/your/files to the actual directory where the files are.

To run, on a mac you should simply be able to run:

python /path/to/your/deduplicate.py

Where /path/to/your/script is wherever you save this .py file. Basically, put the following in a text file and name it deduplicate.py:

import os
import hashlib

path = '/path/to/your/files'


class Deduplicator:
    def __init__(self, path, dry_run=True):
        self.path = path
        self.file_dict = dict()
        self.dry_run = dry_run

    def deduplicate(self):
        self.get_files()
        self.clean_files()

    def get_file_hash(self, file_path):
        with open(file_path, 'rb') as f:
            file = f.read()
            hash = hashlib.sha256(file).hexdigest()
            return hash

    def get_files(self):
        # Loop through the directory
        for file in os.listdir(path):
            # Get the file hash
            hash = self.get_file_hash(os.path.join(self.path, file))

            # If we haven't seen the hash yet, go ahead and initiate the list
            if not self.file_dict.get(hash):
                self.file_dict[hash] = list()

            # Then add this filename to that hashed value
            self.file_dict[hash].append(file)


    def clean_files(self):
        for hash, file_names in self.file_dict.items():
            file_names.sort(reverse=True)
            files_to_delete = file_names[:-1]
            print(f"File to keep: {file_names[0]}")
            print(f'Files to delete: {files_to_delete}')
            print('-'*50)
            if not self.dry_run:
                for file in files_to_delete:
                    full_path = os.path.join(path,file)
                    print(f"...deleting: {full_path}")
                    os.remove(full_path)


if __name__ == '__main__':
    d = Deduplicator(path, dry_run=False)
    d.deduplicate()
0

A very unpopular opinion is to use a dedicated duplicate removal software rather than trying to mess up with terminal. Even if you are pro-terminal user you might end up deleting important files.

There are few reasons.

  1. Softwares are developed after succeeding a lot of cases.
  2. They compare file content rather than relying on file name only.
  3. They provide an interface that draw attention to important details.

You must log in to answer this question.

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