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

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.