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.

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

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
  • it seems to copy all subfolders... – fonfonx May 9 '16 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 ? – mazs May 9 '16 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 '16 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. – mazs May 9 '16 at 9:44
  • 3
    @mazs : Instead of the if statement you may consider adding -mindepth 1 for find. – sjsam May 9 '16 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.

  • 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. – Gilles 'SO- stop being evil' May 9 '16 at 20:49
  • @Gilles : I have changed the script to address the issues you have pointed out :D – sjsam May 10 '16 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 '16 at 11:36
  • @unop . Edited. This one works for folders with newlines and folders that start with a .. – sjsam May 10 '16 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.