3

I have this script:

#!/bin/bash
function main {
  while read -r file; do
    do_something "$file"
  done <<< $(find . -type f 2>/dev/null)
}
function do_something{
   echo file:$@
}

On linux, it works fine, but on Mac (Bash version 5.2), it treats all files found as one item, and pass the whole string without line-break to do_something. And if I run this:

  while read -r file; do
    echo file:"$file"
  done <<< $(find . -type f 2>/dev/null)

directly in Bash terminal on Mac, it also works fine. So what goes wrong?

2
  • 2
    That sort of redirection should be using process substitution instead of herestring + command substitution: done < <(find . -type f 2>/dev/null)
    – muru
    Jun 9 at 6:42
  • That sort of redirection into a while loop shouldn't be done at all. See Why is looping over find's output bad practice?. Use readarray (AKA mapfile instead) to populate an array (using NUL as the filename separator) and then iterate over the array - e.g. readarray -d '' -t files < <(find . -type f -print0 2>/dev/null), followed by for f in "${files[@]}" ; do ... ; done
    – cas
    Jun 9 at 10:44

1 Answer 1

7

In older versions of bash, in <<< $param$((arith))$(cmdsubst) where <<< is the here-string operator copied from zsh, such unquoted expansions were subject to $IFS-splitting and the resulting words joined back with space and the result stored in the temporary file which makes up the target of the redirection.

That was fixed in 4.4. See corresponding entry in CWRU/changelog:

2015-09-02

redir.c
- write_here_string: don't word-split the here string document. The bash documentation has always said this doesn't happen, even though bash has done so for years, and other shells that implement here- strings don't perform any word splitting. The practical effect is that sequences of IFS characters are collapsed to spaces. Fixes bug reported by Clint Hepner <[email protected]>

But macos still uses an ancient version of bash. You might have a newer bash installed somewhere else but AFAIK, there, /bin/bash as used in your shebang is 3.2.x, not 5.2.

While quoting the $(find...) would address that particular issue, here it's definitely the wrong way to iterate over find's output.

See why and correct alternatives at Why is looping over find's output bad practice?

Namely, if you have to use a bash loop (would also work in zsh):

while IFS= read -rd '' -u3 file; do
  something with "$file"
done 3< <(find . -type f -print0 2> /dev/null)

(process substitution, -r, -u, -d, all copied from ksh were all introduced before or in the same version as <<< in 2.05b, so should be available in macos' /bin/bash)

Since macos has had zsh preinstalled since forever, you could also switch to that and just write:

for file (**/*(ND.)) something with $file

See also:

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .