0

Question

Why do shells implement alternative means like <<<, < <(command) and < /dev/fd/* to redirect something to stdin when pipes do exist?

Example

The | way (classic pipes)

echo 'text' | sed 's/x/y/'
# or
cat - | sed 's/x/y/' # type text afterwards

The <<< way

sed 's/x/y/' <<< 'text'

The < <(command) way

sed 's/x/y/' < <(echo 'text')
# while <(command) becomes a file descriptor like
sed 's/x/y/' < /dev/fd/42

All of them return teyt.

Semnodime
  • 347
  • So you're saying we should be doing tr ... < <(cat some-file) or cat some-file | tr ... instead of just tr ... < some-file? Why? – muru Mar 15 '23 at 03:57
  • Convenience and nuances (example). – Kamil Maciorowski Mar 15 '23 at 06:13
  • It seems that mentioning process substitutions here is out of place as they are not in themselves redirections. The redirection you show is an ordinary one, with <, redirecting the process substitution, which is a pathname. This makes it no more "special" than redirecting from some file, just like with redirecting from stuff beneath /dev/fd. – Kusalananda Mar 15 '23 at 06:38

3 Answers3

2

Sotto goes into largely aesthetic reasons, but there are also technical ones. For example, pipes typically result in a subprocess for the right hand side, which means that you cannot set states or variables that you expect to be available later there. Compare:

$ echo foo > file
$ line=bar; cat file | read -r line; echo "$line"
bar
$ line=bar; read -r line < file; echo "$line"
foo
Chris Down
  • 125,559
  • 25
  • 270
  • 266
  • Are there exceptions to the right-hand subprocess rule? Or why say "typically"? – Semnodime Mar 25 '23 at 09:58
  • @Semnodime Shells are free to implement it however they like. For example, if both sides are builtins, it's possible the shell does not use a pipe at all. – Chris Down Mar 26 '23 at 04:56
  • When I pointed out that simple input redirection pre-dates the alternatives cited in the question, it's functionality. Quite a bit more than aesthetics. – Sotto Voce Mar 26 '23 at 05:40
1

Because a pipe must be fed by a process. The < redirection doesn't need a process, it can be fed by a file. The < redirection was also created long before the <<< and <( ...) operations were invented.

And it's also a complement (counterpart) to the > output redirection. Having basic functions that are complementary to each other is a good thing.

Sotto Voce
  • 4,131
  • 1
    In addition to being more efficient than cat somefile |..., input redirection from a file with < is also more flexible. For example, you can't use seek() on a pipe (to skip forward or backward in it), but you can on a file read via <. So e.g. cat hugefile | tail can take much longer than tail <hugefile because that can start at the end and work backward. – Gordon Davisson Mar 15 '23 at 07:25
  • @GordonDavisson The answer regarding the seek functionality made quite an impression in my mind, I'd like to encourage you to create an isolated answer from that comment, I'd like to vote it up explicitly, as comments are not meant to be provide space for permanent content. – Semnodime Mar 25 '23 at 09:46
1

When redirecting input from a file, using program <file has several advantages over cat file | program. In addition to efficiency (as Sotto Voce noted), it also gives the program direct access to the file, which lets it do things other than just read it from beginning to end. Some examples:

  • It can read from the file out of order. If you use cat hugefile | tail, the tail program must read through the entire file to get to the end, but if you use tail <hugefile, it can use lseek() to skip to near the end of the file, and read backward until it has what it needs.

  • It can read only part of the file without causing trouble. If you use head <hugefile, head will read what it needs, and then exit (which closes the file); no muss, no fuss. But if you use cat hugefile | head, when head exits and closes the pipe, cat will still be trying to push data into the now-destinationless pipe. To solve this, the system sends the SIGPIPE signal to cat. Many programs will print an error message when they get SIGPIPE; the versions of cat I've tested don't do that, but they do exit with an error status. If this is in a script that sets -e and pipefail modes (as in "Unofficial Bash Strict Mode"), bash will treat this as a fatal error and exit the script. (This sort of thing is why I don't recommend set -e.)

  • It can access the file's properties. If you use pv <hugefile | slowprocessor, pv (the "pipe viewer" utility) will check the file's size, and give you a progress bar showing what percentage of the file has been sent so far, and also an estimated time to completion. But if you use cat hugefile | pv | slowprocessor, pv will have no idea how big the file is and only show the absolute amount that's been sent. (Note: pv does have a -s option that lets you explicitly tell it how big you think the file is.)

So overall, having direct access to the input file allows the program a lot more flexibility in how it uses the file.

Also, many programs (including all of the ones I've used as examples here) let you specify input files directly as command arguments (e.g. tail hugefile instead of either tail <hugefile or cat hugefile | tail). This allows the program even more information and control over how it accesses the file. It also (again, for commands that support this) allows multiple input files (like cat | would). So for commands that support it, it is usually preferred over either a pipe or input redirection.