0

I need to write a shell script, the output of which is fed to the standard input of another application. The shell script needs to read data from a named pipe pipe.fifo.

A trivial way to do this is using

cat pipe.fifo

However, this introduces a slight overhead due to the use of cat command which introduces another pipe. I thought of a way to redirect the data from the named pipe to standard output using only exec and shell redirections and without using any other command.

exec 3<pipe.fifo 3>&1

This should create a new fd 3, representing the opened named pipe pipe.fifo and the data coming from the pipe should be redirected to standard output. When I run this shell script, it blocks as nothing is writing to the named pipe, as expected. However, as soon as I printf something into the named pipe from another shell, the script does not output anything and stops blocking.

I would like to ask, what could be the possible problem in this command, and if such a redirection is even possible.

EDIT: To clarify more on the specific use case, I would like to do something akin to myapp <temp.fifo, where myapp reads directly reads from the named pipe instead of standard input. But unfortunately, I can't call myapp directly. Instead, the application runs a command itself, the output of which is used in the application. Here's some minimal C code which can be used to represent myapp.

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    char command[100];
    char buffer[1024];
    FILE* pipe;

    if (argc != 2) {
        printf("Usage: %s command\n", argv[0]);
        exit(1);
    }

    snprintf(command, sizeof(command), "%s", argv[1]);

    /* Open the command as a pipe */
    pipe = popen(command, "r");

    if (pipe == NULL) {
        printf("Failed to run command\n");
        exit(1);
    }

    /* Read the output of the command into a buffer */
    while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
        printf("%s", buffer);
    }

    pclose(pipe);

    return 0;
}

It accepts the command to run as the first command line argument, which in this case is the mentioned shell script. We can run it as ./myapp ./script.sh assuming both the executable and the script are in the same folder.

I prefer not to modify the actual code of myapp to solve the problem, due to the code being too complex.

Flanas
  • 1
  • 1
  • Redirect is not reading. Cat reads. Redirect does nothing by itself – KamilCuk Apr 30 '23 at 07:09
  • @KamilCuk Sorry if I got this wrong. I would like to do something akin to `myapp – Flanas Apr 30 '23 at 07:31
  • Consider asking about myapp and your specific problem. You seem to have asked xy question. It all depends an myapp. There are tools that accept `-`, there are tools that not. There is also a processs substitution in bash like for example `diff <(cat <&3) - <&4`. That all seems unrealted to your question in this thread – KamilCuk Apr 30 '23 at 07:37
  • https://stackoverflow.com/questions/5782279/why-does-a-read-only-open-of-a-named-pipe-block Opening a named fifo for reading without writer is just blocking, it is what it is. – KamilCuk Apr 30 '23 at 07:39
  • What does the actual reading code look like? Please provide a [mre] – tripleee Apr 30 '23 at 08:10
  • @tripleee I updated the question to include the code. – Flanas Apr 30 '23 at 12:15
  • `exec 3&1` will attempt to open the pipe for fd 3 first, which blocks until the fifo has a producer. After the producer is started, the redirect completes and then the shell immediately closes it when doing the `3>&1` redirect. – William Pursell Apr 30 '23 at 14:42

2 Answers2

1

However, this introduces a slight overhead due to the use of cat command which introduces another pipe

There is no overhead. There is no another pipe. Cat reads the data and writes them to stout.

You can read the data in bash, however bash is not able to handle zero bytes, so it is not great, and it will be line buffered and very incredibly slow compared to cat. You could read char by char -n1 in bash, but then it will be incredibly very super slow.

while IFS= read -r l; do printf "%s\n" "$l"; done < pipe.fifo

The fastest way on Linux would be to write a C program that calls splice https://man7.org/linux/man-pages/man2/splice.2.html . There are cat programs on github that do exactly that . Then the overhead of copying the data to/from kernel space is removed. I doubt you require that performance.

what could be the possible problem in this command,

The 3>&1 removes previous redirection. Now fd3 is redirected to stdout and pipe.fifo is closed and no longer used.

Redirection is a link, not an action. You still have to read from fd3 if you would redirect it.

exec 3<pipe.fifo
cat <&3

Consider researching what is a file descriptor, and how does it differ from file description. It's just a number that refers to an open file.

To read from something and to write to stdout you have to have an entity to do that action. cat is an undisputed champion in exactly that and it surely is intended to have lowest overhead while being portable everywhere.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • 1
    @Philippe The `cat` will execute after the `exec` completes, which will happen when there is a producer writing data to the fifo. – William Pursell Apr 30 '23 at 16:07
0

If the shell script needs to read all of its input from the fifo, just do

exec < pipe.fifo

If the shell only needs that data for the other command, just do:

< pipe.fifo other-command

The line exec 3<pipe.fifo 3>&1 does not do what you want at all. It simply instructs the shell to do 2 redirections, the second of which overrides the first. When the shell evaluates that line, it first tries to dup fd 3 from the pipe, which blocks until some other process is writing to the pipe. As soon as another process starts writing to the pipe, that redirect completes (unblocks) and the shell immediates tries to complete the redirect specified by 3>&1. Executing that redirect closes the fifo and the end result is exactly the same as if the line were just exec 3>&1.

William Pursell
  • 204,365
  • 48
  • 270
  • 300
  • @Philippe The script can read from the fifo just fine. `exec < pipe.fifo` will only block until there is some producer writing to the fifo. – William Pursell Apr 30 '23 at 16:06