3

I would like to recursively search a directory tree and list the number of files available in each folder that have a name corresponding to a given string. In effect, I would like to return results of:

ls -l | wc -l

for each folder I've identified via find.

Code

The code below correctly lists number of files in each folder that name contains Magic Data string.

find /path/to/cool/stuff                        \         
        -maxdepth 4                             \         
        -type d                                 \         
        -name '*Magic Data*'                    \         
        -print0 | xargs                         \         
                --verbose                       \         
                -0 -I {} ls -l {}               

I would like to expand it and pipe results of ls to wc -l.

Attempt

find /path/to/cool/stuff                        \         
        -maxdepth 4                             \         
        -type d                                 \         
        -name '*Magic Data*'                    \         
        -print0 | xargs                         \         
                --verbose                       \         
                -0 -I {} ls -l {}  | wc -l            

This fails and prints:

ls -l /path/to/cool/stuff/some/folders/Magic Data
ls -l /path/to/cool/stuff/some/folders2/Magic Data

Desired output

/path/to/cool/stuff/some/folders/Magic Data  29
/path/to/cool/stuff/some/folders2/Magic Data 30

Desired output would contain:

  • Full path to the searched folder
  • Output of wc -l, which in this case reflects count of non-hidden files
3

There are two main ways of solving this:

  1. Modify the find command so that it only enters the directories that you are interested in and then prints a single character (e.g. x) for each found file within. Then count the number of that character produced with wc -l. It's safer to output a character than the pathname as pathnames in Unix could potentially contain newlines. This solution is a bit tricky as it involves using -prune to ignore directories we're not interested in, or alternatively ! -path.

  2. Find the directories just like you've done so far, but then use an in-line script to do the counting of files within. This is simpler, and what I'm showing below.

find /path/to/cool/stuff -maxdepth 4 -type d -name "*Magic Data*' \
    -exec bash -O dotglob -c '
        dir=$1
        set -- "$dir"/*
        printf "%s %d\n" "$dir" "$#"' bash {} ';'

Here, we find the directories like you do, then, for each directory, we run this short bash script:

dir=$1
set -- "$dir"/*
printf "%s %d\n" "$dir" "$#"

This takes the pathname of the directory from the command line (given by find), and expands the * glob pattern within it. By setting the dotglob shell option on the command line for the script, we are guaranteed to also count hidden files and directories (remove -O dotglob to not count hidden names).

We do the expansion of all names as an argument to set, which will set the positional parameters to the expanded entries. The number of positional parameters is available as $#, which is therefore also the count of files in that particular directory.

Related:

1

Starting from your attempted solution, this is how one could accomplish this task:

find /path/to/cool/stuff                        \         
        -maxdepth 4                             \         
        -type d                                 \          
        -name '*Magic Data*'                    \
        -exec sh -c '
           for d do
              printf "%s: " "$PWD/$d"
              find "$d/." -maxdepth 1 ! -name . -printf "\\n" | wc -l
           done
       ' find_sh {} +

Explanation:

  • Up until the point of find-ing the target directories, your recipe is followed.
  • Once we locate the target directories, meaning those which have the string "Magic Data" somewhere in their basenames, we gather them in a bunch {} +, and hand them over to the shell sh via it's arguments list: sh -c '...' find_sh {} +
  • The initial argument of sh which is the zeroth argument $0 and as such it can be given any name, and here in this case we name it find_sh. This is the name that'll show up in the process list.
  • The target directories bunch goes inside the {} + and form the $1 $2 $3 ... arguments to the shell we are spawning.
  • Inside that shell we just launched, we emit a newline for every file found in the target directories except itself, i.e., ! -name . OTW, the target directory name also gets counted into the wc -l output.
0

You can also use the following command to achieve what you want

Here I am using exec to execute sh shell which is then executing command given to it by using -c option . In your case the command is ls -1 | wc -l

find /path/to/cool/stuff    -maxdepth 4  -type d  -name '*Magic Data*' -print0 -exec sh -c "ls -1 {} | wc -l" \;

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.