0

I have a bunch of directories with a bunch of files in them and a text file. It looks like this

MainDir
|------dir1
|       |-------file1
|       |-------file2
|       |-------file3.txt
|------dir17
|       |-------file23
|       |-------file34
|       |-------file52.txt
|       |-------file65

Number of files in each dir is random and only 1 or none txt file.
I need to move all txt files to "txtFiles" dir inside MainDir and every txt file needs to be in a subdir with the same name as the dir it came from (the subdirs in txtFiles dir does not exist so they need to be created). This is how it should look line

MainDir
|------dir1
|       |-------file1
|       |-------file2
|------dir17
|       |-------file23
|       |-------file34
|       |-------file65
|------txtFiles
|       |-------dir1
|       |        |-------file3.txt
|       |-------dir17
|       |        |-------file52.txt

Is there way to do it with one find command?

Also some of the files and dirs names have whitespaces in them!!!

0
$ tree
.
`-- MainDir
    |-- dir1
    |   |-- notes.txt
    |   |-- some_file-1
    |   |-- some_file-2
    |   `-- some_file-3
    `-- dir17
        |-- README.txt
        |-- some_file-1
        |-- some_file-2
        `-- some_file-3

3 directories, 8 files

Using rsync:

$ rsync -a --include '*.txt' --exclude '*/*' MainDir/ MainDir/txtFiles/
$ tree
.
`-- MainDir
    |-- dir1
    |   |-- notes.txt
    |   |-- some_file-1
    |   |-- some_file-2
    |   `-- some_file-3
    |-- dir17
    |   |-- README.txt
    |   |-- some_file-1
    |   |-- some_file-2
    |   `-- some_file-3
    `-- txtFiles
        |-- dir1
        |   `-- notes.txt
        `-- dir17
            `-- README.txt

6 directories, 10 files

The command uses --include '*.txt' to include all files with .txt file name suffix, and --exclude '*/*' to exclude other files. By using -a or --archive, a copy of the part of the source hierarchy that is copied is created at the destination.

Running this again would result in an empty MainDir/txtFiles/txtFiles directory being created. Adding -m or --prune-empty-dirs to the rsync invocation would avoid that.


Using find (from within MainDir):

$ find . -type f -name '*.txt' ! -path '*/txtFiles/*' \
    -exec sh -c 'for n; do
                     d="txtFiles/${n%/*}";
                     mkdir -p "$d" && cp "$n" "$d"
                 done' sh {} +

This finds all regular files whose names matches *.txt and that are not in a txtFiles directory.

For each found filename path, the directory part of the path is created under txtFiles and the file is copied there.

The only difference between the rsync approach and this is that rsync may copy non-regular files that matches the given name (directories named pipes etc.), and that the find command must be run from within MainDir.

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.