Search code examples
bashcommand-lineterminalshellcheck

Iterate over specific files in a directory using Bash find


Shellcheck doesn't like my for over find loop in Bash.

for f in $(find $src -maxdepth 1 -name '*.md'); do wc -w < "$f" >> $path/tmp.txt; done

It suggests instead:

1  while IFS= read -r -d '' file
2  do
3      let count++
4      echo "Playing file no. $count"
5      play "$file"
6  done <   <(find mydir -mtime -7 -name '*.mp3' -print0)
7  echo "Played $count files"

I understand most of it, but some things are still unclear.

In line one: What is '' file?

In line six: What does the empty space do in < < (find). Are the < redirects, as usual? If they are, what does it mean to redirect into do block?

Can someone help parse this out? Is this the right way to iterate over files of a certain kind in a directory?


Solution

  • In line one: What is '' file?

    According to help read, that '' is an argument to the -d parameter:

    -d delim    continue until the first character of 
                DELIM is read, rather than newline
    

    In line six: What does the empty space do in < < (find).

    There are two separate operators there. There is <, the standard I/O redirection operator, followed by a <(...) construct, which is a bash-specific construct that performs process substitution:

    Process Substitution
    
        Process  substitution  is  supported on systems that
        support named pipes (FIFOs) or the /dev/fd method of naming
        open files.  It takes the form of <(list) or >(list).  The
        process list is run with its  input  or output  connected
        to  a FIFO or some file in /dev/fd...
    

    So this is is sending the output of the find command into the do loop.

    Are the < redirects, as usual? If they are, what does it mean to redirect into do block?

    Redirect into a loop means that any command inside that loop that reads from stdin will read from the redirected input source. As a side effect, everything inside that loop runs in a subshell, which has implications with respect to variable scope: variables set inside the loop won't be visible outside the loop.

    Can someone help parse this out? Is this the right way to iterate over files of a certain kind in a directory?

    For the record, I would typically do this by piping find to xargs, although which solution is best depends to a certain extend on what you're trying to do. The two examples in your question do completely different things, and it's not clear what you're actually trying to accomplish.

    But for example:

    find $src -maxdepth 1 -name '*.md' -print0 |
      xargs -0 -iDOC wc -w DOC
    

    This would run wc on all the *.md files. The -print0 to find (and the -0 to xargs) permit this command to correctly handle filenames with embedded whitespace (e.g., This is my file.md). If you know you don't have any of those, you just do:

    find $src -maxdepth 1 -name '*.md' |
      xargs -iDOC wc -w DOC