I have a folder root_folder
containing a lot of subfolders. Each of these subfolders contains a small number of files (between 1 and 20) and I want to copy all the subfolders containing at least 5 files into another folder new_folder
. I have found how to print the folders that interest me: https://superuser.com/questions/617050/find-directories-containing-a-certain-number-of-files but not how to copy them.
3 Answers
You can do a for loop on the find result and copy the folder with -R :
IFS=$'\n'
for source_folder in "$(find . -maxdepth 1 -type d -exec bash -c "echo -ne '{}\t'; ls '{}' | wc -l" \; |
awk -F"\t" '$NF>=5{print $1}');" do
if [[ "$source_folder" != "." ]]; then
cp -R "$source_folder" /destination/folder
fi
done
-
-
Yeah depends from where do you execute the script. Execute it from inside root_folder, or replace
find .
withfind /path/to/root_folder/
, does it work that way ?– magorMay 9, 2016 at 9:39 -
1I executed it from inside
root_folder
but thefind
command seems to return also.
in its list and I suppose that is why all subfolders are copied. I am looking for a way to remove it from the list– fonfonxMay 9, 2016 at 9:40 -
1Hmmm yes you are right, the first hit is the
.
folder, I didn't check it thouroughly. I added an if statement to my solution to avoid processing the.
folder.– magorMay 9, 2016 at 9:44 -
3@mazs : Instead of the
if
statement you may consider adding-mindepth 1
forfind
.– sjsamMay 9, 2016 at 9:54
Below script works for your case :
find . -mindepth 1 -maxdepth 1 -type d -print0 | while read -rd '' line
do files=("$line"/* "$line"/.*)
count=${#files[@]};((count-=2))
if [ $count -ge 5 ]
then
cp -R "$line" ../newfolder/
fi
done
Note : This should be executed from the base folder as I am using relative paths.
-
This is gratuitously complex and gratuitously breaks on file names containing spaces. Don't parse the output of
find
, usevar=(*/)
and likewise for the file count. May 9, 2016 at 20:49 -
@Gilles : I have changed the script to address the issues you have pointed out :D– sjsamMay 10, 2016 at 3:04
-
Now this breaks on file names containing newlines. :) Don't parse the output of other commands to do file ops (see mywiki.wooledge.org/ParsingLs). @Gilles already suggested a robust way using an glob and an array.
count=(*/); (( ${#count[@]} > 5 )) && ...
– shalombMay 10, 2016 at 11:36 -
@unop . Edited. This one works for folders with newlines and folders that start with a
.
.– sjsamMay 10, 2016 at 16:40
Iterating over the subdirectories of a directory:
for subdir in root_folder/*/; do
if [ -L "${subdir%/}" ]; then continue; fi
…
done
The if [ -L …
line skips symbolic links to directories. Omit it if you want to include symbolic links to directories or if you know there won't be any.
Directories whose name begins with a .
(dot directories) won't be included. To include them, in bash, run shopt -s dotglob
.
To count the number of files in a directory, in bash, store them in an array and count the number of elements. Run shopt -s nullglob
to get 0 for an empty directory (otherwise the glob pattern *
remains unexpanded if it matches nothing, so you get 1 instead of 0).
Thus:
#!/bin/bash
shopt -s nullglob dotglob
for subdir in root_folder/*/; do
if [ -L "${subdir%/}" ]; then continue; fi
files=("$subdir"/*)
if ((${#files[@]} >= 5)); then
cp -Rp "$subdir" new_folder/
fi
done
..\new_folder
when I am insideroot_folder