3

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.

2
  • is new folder outside the folder that you're working on?
    – sjsam
    May 9, 2016 at 9:41
  • yes, its relative path is like ..\new_folder when I am inside root_folder
    – fonfonx
    May 9, 2016 at 9:42

3 Answers 3

4

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
7
  • it seems to copy all subfolders...
    – fonfonx
    May 9, 2016 at 9:36
  • Yeah depends from where do you execute the script. Execute it from inside root_folder, or replace find . with find /path/to/root_folder/, does it work that way ?
    – magor
    May 9, 2016 at 9:39
  • 1
    I executed it from inside root_folder but the find 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
    – fonfonx
    May 9, 2016 at 9:40
  • 1
    Hmmm 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.
    – magor
    May 9, 2016 at 9:44
  • 3
    @mazs : Instead of the if statement you may consider adding -mindepth 1 for find.
    – sjsam
    May 9, 2016 at 9:54
3

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.

4
  • This is gratuitously complex and gratuitously breaks on file names containing spaces. Don't parse the output of find, use var=(*/) 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
    – sjsam
    May 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 )) && ...
    – shalomb
    May 10, 2016 at 11:36
  • @unop . Edited. This one works for folders with newlines and folders that start with a ..
    – sjsam
    May 10, 2016 at 16:40
2

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

You must log in to answer this question.

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