3

I want to pipe the output of a program A in a program B. The program B doesn't support reading from stdin, only from files. Can I simply do A | B /dev/stdin ?

In fact it seems it works, but I want to make sure that when running the pipeline, the only things B gets from /dev/stdin are what A has written.

uuu
  • 31

2 Answers2

2

Here, in

A | B /dev/stdin

since B's stdin is a pipe¹, /dev/stdin will behave like a named pipe.

So it will only work properly if B opens the file once and reads its contents sequentially from the start, not if:

  • it's checking its type beforehand to verify it's a regular file (here the file will appear as a named pipe on Linux and a character device on most other systems).
  • it's checking its size beforehand
  • it opens it several times
  • it needs to seek (change cursor position) within the file (rewind, move to the end...)
  • it needs to mmap() it, or any other operation that is valid on a regular file but not on a pipe.

And of course B must not also read from its stdin. Using a different file descriptor, for instance using process substitution (B <(A) in ksh/zsh/bash) would avoid that problem. That also means B must not arbitrarily close all its file descriptors (like some commands such as sudo do for fds above 2).

While most commands will just read files sequentially from start to end, several of those that only take input from filename arguments and not from stdin do that because actually they can't for any ones of the reasons listed above, so your mileage may vary.

$ unzip -l file.zip
Archive:  file.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
    48894  2020-12-05 07:35   file
---------                     -------
    48894                     1 file
$ cat file.zip | unzip -l /dev/stdin
Archive:  /dev/stdin
  End-of-central-directory signature not found.  Either this file is not
  a zipfile, or it constitutes one disk of a multi-part archive.  In the
  latter case the central directory and zipfile comment will be found on
  the last disk(s) of this archive.
unzip:  cannot find zipfile directory in one of /dev/stdin or
        /dev/stdin.zip, and cannot find /dev/stdin.ZIP, period.

infozip files are a kind of file that can't be processed sequentially one needs to read an index at the end of the file first before accessing the actual data within the file.

¹ As a corner case, note that the ksh93 shell on Linux uses socketpairs instead of pipes (as pipes there are not peekable). And since on Linux, /dev/stdin is not a special character device like on other systems, but a "magic" symlink to the actual file, opening /dev/stdin in that case will never work as it's not possible to open a socket.

$ ksh93 -c 'echo test | cat /dev/stdin'
cat: /dev/stdin: No such device or address

With ksh93, you'll want to use process substitution which uses pipes regardless of the system.

$ ksh93 -c 'cat <(echo test)'
test
0

(See also JdeBP's comment)

Yes, but the recommended way is to use /dev/fd/0. It is mentioned e.g. in the book "Advanced Programming in the UNIX Environment":

It allows programs that use pathname arguments to handle standard input and standard output in the same manner as other pathnames.

/dev/fd/0 and /dev/stdin are the same device. See here for plenty of useful information:

VPfB
  • 801
  • 3
    The question is phrased as a general case, and the general case answer is no. The program might be expecting the descriptor that it opens to the file to be seekable. Or writable. Or write lockable. Or it might be written to check the file with fstat() and abend if it finds anything other than a regular file. – JdeBP Jul 27 '17 at 21:20
  • @JdeBP Your arguments are correct, of course, but I did not understand the question the same way as you. See the second paragraph, isn't that what the OP means as "safe" in the title? Anyway, I have added a quote for clarification. – VPfB Jul 28 '17 at 05:12
  • AFAIK, /dev/stdin is more portable than /dev/fd/0, though on modern systems, that should make no difference. – Stéphane Chazelas Dec 05 '20 at 07:27