0

Create an empty directory, then run this code:

find /empty_directory -mindepth 1 -maxdepth 1 -type d |
while read dir;
do
    echo "Simple find: $dir"
done

printf '%s\n' '-----' # Safer than echo -----

while read dir; do echo "HereDoc find: $dir" done <<< "$(find /empty_directory -mindepth 1 -maxdepth 1 -type d)"

This is the output:

-----
HereDoc find: 

In other words, the second loop gets executed once with an empty variable.

Why is it so? How can I make the second loop behave correctly and not run when there is no directory found to be looped over?

1 Answers1

5

What’s happening?

I understand that you’re trying to solve a particular problem, but one of the first rules of debugging is to simplify the scenario.  If we do

$ sleep 1 | read x
$ echo "$?"
1

$ read x <<< $(sleep 1) $ echo "$?" 0

we see that a read from an empty pipe fails, but a read from a null here string succeeds (reading a null value).  That is what you are seeing.

And it’s spelled out in bash(1):

Here Strings

    A variant of here documents, the format is:

        [n]<<<word

    The word undergoes tilde expansion, parameter and variable expansion, command substitution, arithmetic expansion, and quote removal.  Pathname expansion and word splitting are not performed.  The result is supplied as a single string, with a newline appended, to the command on its standard input (or file descriptor n if n is specified).

      (emphasis added)

Bash turns a null here string into a blank line, not an EOF.

How to fix it?

The obvious — modify the loop logic:

while read dir  && [ "$dir" != "" ]
do
    echo "HereDoc find: $dir"
done <<< "$(find /empty_directory -mindepth 1 -maxdepth 1 -type d)"
This terminates the loop if it reads a blank line.

Equally obvious — modify the logic in the loop:

while read dir
do
    if [ "$dir" = "" ]; then continue; fi
    echo "HereDoc find: $dir"
done <<< "$(find /empty_directory -mindepth 1 -maxdepth 1 -type d)"
This ignores blank lines (but doesn’t terminate the loop).

Perhaps better — modify the redirection:

while read dir
do
    echo "HereDoc find: $dir"
done < <(find /empty_directory -mindepth 1 -maxdepth 1 -type d)
By using process substitution (<(command)) and simple redirection (<) instead of command substitution ($(command)) and a here string (<<<), you prevent Bash from adding the newline.

P.S. Note that you don’t need to have a semicolon (;) at the end of a line, as you have done with while read dir.