2

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).

1
  • 1
    Probably somthing like rsync --include=*_ext.dat /main_dir/ /new_dir/ will do the trick. Haven't tested it though. Oct 9, 2013 at 0:24

1 Answer 1

1

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 *
8
  • 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? Nov 11, 2013 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.
    – The Vee
    Nov 11, 2013 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.
    – The Vee
    Nov 11, 2013 at 11:05
  • Thanks, though, for the comment on spaces. Fixed.
    – The Vee
    Nov 11, 2013 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 Nov 11, 2013 at 11:20

You must log in to answer this question.

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