What does "< <(command args)" mean in the shell?

BashShellBuilt In

Bash Problem Overview


When looping recursively through folders with files containing spaces the shell script I use is of this form, copied from the internet:

    while IFS= read -r -d $'\0' file; do
      dosomethingwith "$file"        # do something with each file
    done < <(find /bar -name *foo* -print0)

I think I understand the IFS bit, but I don't understand what the '< <(...)' characters mean. Obviously there's some sort of piping going on here.

It's very hard to Google "< <" or "<(", you see. I tried "angle bracket parenthesis" and "less-than parenthesis" but didn't find anything.

Bash Solutions


Solution 1 - Bash

<() is called process substitution in the manual, and is similar to a pipe but passes an argument of the form /dev/fd/63 instead of using stdin.

< reads the input from a file named on command line.

Together, these two operators function exactly like a pipe, so it could be rewritten as

find /bar -name *foo* -print0 | while read line; do
  ...
done

Solution 2 - Bash

<( command ) is process substitution. Basically, it creates a special type of file called a "named pipe," then redirects the output of the command to be the named pipe. So for example, suppose you want to page through a list of files in an extra-big directory. You could do this:

ls /usr/bin | more

Or this:

more <( ls /usr/bin )

But NOT this:

more $( ls /usr/bin )

The reason for this becomes clear when you investigate further:

~$ echo $( ls /tmp )
gedit.maxtothemax.436748151 keyring-e0fuHW mintUpdate orbit-gdm orbit-maxtothemax plugtmp pulse-DE9F3Ei96ibD pulse-PKdhtXMmr18n ssh-wKHyBU1713 virtual-maxtothemax.yeF3Jo
~$ echo <( ls /tmp )
/dev/fd/63
~$ cat <( ls /tmp )
gedit.maxtothemax.436748151
keyring-e0fuHW
mintUpdate
orbit-gdm
orbit-maxtothemax
plugtmp
pulse-DE9F3Ei96ibD
pulse-PKdhtXMmr18n
ssh-wKHyBU1713
virtual-maxtothemax.yeF3Jo

/dev/fd/whatever acts like a text file with the output of the command between the parenthesis.

Solution 3 - Bash

< redirects to stdin.

<() seems to be some sort of a reverse pipe, as mentioned on the page:

find /bar -name *foo* -print0 | \
while IFS= read -r -d $'\0' file; do
  dosomethingwith "$file"        # do something with each file
done

will not work, because the while loop will be executed in a subshell, and you'll lose changes made in the loop

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionstibView Question on Stackoverflow
Solution 1 - BashJosh LeeView Answer on Stackoverflow
Solution 2 - BashMax E.View Answer on Stackoverflow
Solution 3 - BashknittlView Answer on Stackoverflow