1

I've seen both <<<'foo' and <(echo 'foo') suggested as alternatives to echoing to pipes (echo foo | cat).

When I use both of these constructs with cat I get:

$ cat <<<'foo'
foo
$ cat <(echo 'foo')
foo

So far, so expected.

A difference becomes apparent however when you use them on the shell directly:

$ <<<'foo'
foo
$ <(echo 'foo')
zsh: permission denied: /dev/fd/12

I am using zsh, and bash seems to treat the <<< case differently, printing nothing at all, but is the same as zsh in the <() case.

But what actually triggered my question was a diff:

$ diff <<<"/mypath" <(pwd)
diff: missing operand after `/dev/fd/63'
diff: Try `diff --help' for more information.

It's clear there's a fundamental difference between these two types of shell redirection. Can anyone explain the difference?

  • As far as I can tell, <() is a sub-shell being replaced with a string, and is not being attached to stdin of the process, whereas <<< does attach to stdin like < does, but I'm not 100% on this, so an authoritative answer that explains the fundamentals would be beneficial to many I think! – Alex Forbes Aug 09 '19 at 12:36
  • <() is "process substitution", <<< is a herestring. Look up the docs for those and you will see they're quite unrelated. – muru Aug 09 '19 at 12:46
  • 4
    Possible duplicate of What are the shell's control and redirection operators? and https://unix.stackexchange.com/questions/22645/what-does-a-redirection-mean/22646 – muru Aug 09 '19 at 12:48
  • Agree that the linked wiki covers this, but I don't agree that it's a duplicate question. Yes, I could find the answer in documentation or the linked wiki answer, but surely the point of Q&A sites such as this is to ask and answer more specific questions about a topic? If this question is a duplicate of that wiki answer, then so is every question tagged io-redirection. Practically every question on the site could be tagged as a dupe of a man page. – Alex Forbes Aug 09 '19 at 15:43
  • Wells you'll have fun going through the dupes of that question: https://unix.stackexchange.com/questions/linked/159513?lq=1 – muru Aug 09 '19 at 15:44
  • As you are a highly active user I can understand your frustration, but a lot of those dupes have a fair few up-votes, which shows they have value to readers – Alex Forbes Aug 09 '19 at 15:49
  • 1
    You may be interested in https://askubuntu.com/a/678919/295286 as well. – Sergiy Kolodyazhnyy Aug 09 '19 at 17:12

1 Answers1

3

<<< text is a redirection, if affects the file descriptors of a command (here 0, but 4<<< text would affect the fd 4 instead).

While <(...) is an expansion/substitution, not a redirection. That expands to a file name. That affects the arguments passed to a command if inserted there.

cat <(echo test)

Runs cat with a file like /dev/fd/12 as argument. And as it happened also with its fd 12 being open on the reading end of a pipe. cat will not read from its fd 12, but when it opens the /dev/fd/12 file, it will get a new fd (maybe 3) that points to the same pipe as fd 12.

Meanwhile echo test is run in background with its fd 1 connected to the writing end of that same pipe, so echo and cat will end up being connected via that pipe (though the establishment of that communication channel is much more convoluted than with echo text | cat).

cat <<< text

Runs cat without argument and with its fd 0 open for reading on a deleted temporary file that contains test\n.

zsh interprets

<<< test

as

$NULLCMD <<< test

($NULLCMD being typically cat) as happens any time you run something with only redirections (except when there's only one < redirection, in which case $READNULLCMD (typically a pager) is used instead)).

In:

<(echo text)

There's no redirection, just one process substitution that expands to one /dev/fd/12 argument. So that's asking the shell to execute that file.

In zsh, there's no real benefit of using:

cat <(echo test)
cat < <(echo test)

over

echo test | cat

You see this construct in other shells like bash in things like:

IFS= read -r var < <(echo text)

but that's because in those shells, echo text | IFS= read -r var would not work because read is run in a child process. That's not the case in zsh.

Process substitution is useful for commands that only accept input via a file name as argument (and not via stdin) of when you need to pass more than one input to a command (there's only one stdin) like in diff <(cmd1) <(cmd2).

One difference in zsh between cmd1 | cmd2 and cmd2 < <(cmd1) is that in the latter, zsh doesn't wait for cmd1. While it does wait for both cmd1 and cmd2 in cmd1 | cmd2 (if only so as to make both exit statues available in $pipestatus. Compare sleep 1 | uname with uname < <(sleep 1).

cmd <<< foo can be useful when cmd needs to be able to seek into it's input (because in zsh, here-strings like here-documents are implemented with temporary files instead of pipes).

  • 1
    This answer is great, but it's missing the explanation for the final diff <<<"/mypath" <(pwd), which is explained by the fact diff still wants two file names and you need to pass an extra - for it to read from stdin, which is what <<<"/mypath" is. (Also, using diff and that many redirections for this is way overkill!) – filbranden Aug 09 '19 at 14:12
  • zsh will wait for cmd1 in cmd2 < <(cmd1) if cmd2 is a built-in, compound command or function. Compare true < <(sleep 2) vs /bin/true < <(sleep 2). bash will only wait for it if it's a compound command or function, not if it's a built-in. See also this. –  Aug 09 '19 at 14:55
  • This is a great answer thank you; exactly what I was looking for. – Alex Forbes Aug 09 '19 at 15:26
  • And yes, diff is way overkill, I just wanted a quick way to determine where the typo was in a particularly long path. Most people would probably just compare the strings manually! – Alex Forbes Aug 09 '19 at 15:36