Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

I have a batch of files all ending with the same string, ie: *_ext.dat located in several sub-dirs along with several other files, in a given main dir. This is the structure:

/main_dir/subdir1/file11_ext.dat
/main_dir/subdir1/file12_ext.dat
/main_dir/subdir1/file13_ext.dat
/main_dir/subdir1/file14_other.dat
/main_dir/subdir1/file15_other.dat

/main_dir/subdir2/file21_ext.dat
/main_dir/subdir2/file22_ext.dat
/main_dir/subdir2/file23_ext.dat
/main_dir/subdir2/file24_other.dat
/main_dir/subdir2/file25_other.dat

/main_dir/subdir3/file31_ext.dat
/main_dir/subdir3/file32_ext.dat
/main_dir/subdir3/file33_ext.dat
/main_dir/subdir3/file34_other.dat
/main_dir/subdir3/file35_other.dat

I need to recursively move only the files ending in *_ext.dat into a new main dir, new_dir, respecting the sub-dir structure so the files will end up in an equivalent dir structure like this:

/new_dir/subdir1/file11_ext.dat
/new_dir/subdir1/file12_ext.dat
/new_dir/subdir1/file13_ext.dat

/new_dir/subdir2/file21_ext.dat
/new_dir/subdir2/file22_ext.dat
/new_dir/subdir2/file23_ext.dat

/new_dir/subdir3/file31_ext.dat
/new_dir/subdir3/file32_ext.dat
/new_dir/subdir3/file33_ext.dat

Because of this the command should also create those sub-dirs with their corresponding names. I know that with a line like this one:

find . -name "*_ext.dat" -print0 | xargs -0 rm -rf

I can delete all those files, but I don't know how to modify it to do what I need (or if it is even possible).

share|improve this question
1  
Probably somthing like rsync --include=*_ext.dat /main_dir/ /new_dir/ will do the trick. Haven't tested it though. –  eldering Oct 9 '13 at 0:24
add comment

2 Answers

The CLI command is

cp -a /path/to/source/dir/* /path/to/target/dir

A couple of comments: the -a option will respect timestamps and ownership, if you do not care for such things you may use the -R option. The source dir is the directory of the files you want to cp. It is important you use the star, otherwise the command will copy, inside the new_dir, the old directory itself. In other words, if you do NOT use the star, you will find a directory structure like this:

new_dir -> main_dir -> subdir1,....

while, if you use the star, you will have:

new_dir -> subdir1,....

Use as needed.

EDIT: (sorry, I forgot) once you have copied your files, you can remove the old tree structure with the command

rm -rf /path/to/source/dir/* 

The -r option is "recursive", i.e., follow the directory tree down to the last leaves and delete everything. The -f option means "do not ask for confirmation". You can use instead -i, if you feel so inclined, which will ask for confirmation for every single object.

EDIT2: Sorry, I did not understand you only wanted to move files of a given extension. Go to the source directory. i.e. the one from which you want to copy the files, and:

find . -name '*.exttobefound' -exec cp --parents {} /path/to/target/directory \;
share|improve this answer
    
I'm sorry Marius, I explained myself poorly. This command will move everything into the new location when I only need certain files to me moved. I've refactored the question to make this clear, sorry again. –  Gabriel Oct 9 '13 at 13:49
add comment

I would first create the subdirectories in new_dir by

cd main_dir
for i in *; do mkdir "../new_dir/$i"; done
cd ..

Then you can use bash's for command again along with pattern expansion to do exactly what you need quickly:

for i in main_dir/*/*_ext.dat; do cp "$i" "new_dir${i##main_dir}"; done

using the fact that the destination directories exist. Finally, if were not guaranteed that each of them would actually be used, you can purge the empty ones afterwards:

cd new_dir
rmdir --ignore-fail-on-non-empty *
share|improve this answer
    
In the first loop you are expecting that in the main_dir there are only directories. Also you are not double-quoting the paths in all your code so it will not work with file names containing spaces. This code will be much more robust: for i in */; do mkdir "../new_dir/$i"; done. --- The last piece of code will not remove directories containing empty directories. For that you can use find: How do you remove nested empty directories using a Bash script on Linux? –  pabouk Nov 11 '13 at 7:59
    
@pabouk Of course it will. (Perhaps you didn't notice the cd preceding it?) Man page of rmdir says: "DESCRIPTION ... Remove the DIRECTORY(ies), if they are empty." The option --ignore-fail-on-non-empty opts out from the warnings that non-empty directories were left out. Moreover, I tried it on my system before posting. –  Vašek Potoček Nov 11 '13 at 11:04
    
Also, the directory structure my commands expect agrees perfectly with the way the OP introduced it including his explicit examples. Of course the commands would be different if the structure was. –  Vašek Potoček Nov 11 '13 at 11:05
    
Thanks, though, for the comment on spaces. Fixed. –  Vašek Potoček Nov 11 '13 at 11:08
    
You are welcome. You exaggerated with the double quotes. The last command will not do what you want. --- First loop: Is not it better to add single / so that the code is more universal and works in more use-cases? --- Last command does not work: mkdir a ; cd a ; mkdir -p a/b/c d/e/f g/h/i ; ls ; rmdir --ignore-fail-on-non-empty * ; ls --- output: a d g a d g –  pabouk Nov 11 '13 at 11:20
show 3 more comments

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.