简体   繁体   中英

Can't use named pipe from C to communicate with shell script

I have a C program like so (copied from here ):

#include <fcntl.h>

#define PATH "testpipe"
#define MESSAGE "We are not alone"

int main()
{
   int fd;

   mkfifo ( PATH, 0666 );

   fd = open ( PATH, O_WRONLY );
   write ( fd, MESSAGE, sizeof ( MESSAGE ) );
   close ( fd );

   unlink ( PATH );
   return 0;
}

and a shell script like so:

echo < testpipe

Once I execute the C program, the echo statement returns, but We are not alone is not printed. I have also tried creating the pipe from the command line, and with mknod instead, and it makes no difference. Why is this not working?

EDIT:

A lot of people have pointed out that the problem is with echo and not with C , problem is, I need it to work with something like echo ( omxplayer actually, as I am issuing video control commands to it, ie. p for pause or q for quit). Therefore, I would appreciate some answers indicating how to change the C code to make it work with the echo statement, as opposed to vice versa

EDIT:

I didn't include the full code as it uses omxplayer and is rather large, but some users requested it, therefore, here is as minimal as I could keep this MWE:

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

#define PIPEPATH "testpipe"
#define VIDEOPATH "Matrix.mkv"
#define MESSAGE "q"
#define VIDEOPLAYER "omxplayer"

int main()
{
   int fd;
   pid_t pid;
   pid_t wpid;
   int status;
   char shellCmd [ 1000 ];
   struct timespec time1, time2; //used for sleeping

   //Make pipe BEFORE forking
   mkfifo ( PIPEPATH, 0666 );

   if ( ( pid = fork () ) < 0 )
   {
      perror ( "Fork Failed\n" );
      return -1;
   }
   else if ( pid == 0 )
   { //first child keeps pipe open
      sprintf ( shellCmd, "tail -f /dev/null > %s", PIPEPATH );
      if ( system ( shellCmd ) == -1 )
      {
         printf ( "Error: %s\n", shellCmd );
         fflush(stdout);
      }
   }
   else{
      time1.tv_sec = 1L; //sleep for 1 second to let first child issue its command
      time1.tv_nsec = 0L; //Dont worry about milli seconds

      nanosleep ( &time1, &time2 );

      if ( ( pid = fork () ) < 0 )
      {
         perror ( "Fork Failed\n" );
         return -1;
      }
      else if ( pid == 0 )
      { //second child launches the movie
         sprintf ( shellCmd,  "cat %s | %s %s 2>&1 > /dev/null", PIPEPATH, VIDEOPLAYER,  VIDEOPATH );
         if ( system ( shellCmd ) == -1 )
         {
            printf ( "Error: %s\n", shellCmd );
            fflush(stdout);
         }
      }
      else
      {
         if ( ( pid = fork () ) < 0 )
         {
            perror ( "Fork Failed\n" );
            return -1;
         }
         else if ( pid == 0 )
         { //third child waits 5 seconds then quits movie
            time1.tv_sec = 5L; //sleep for 5 seconds
            time1.tv_nsec = 0L; //Dont worry about milli seconds

            nanosleep ( &time1, &time2 );

            printf ( "Sleep over, quiting movie\n");
            fflush(stdout);

            fd = open ( PIPEPATH, O_WRONLY );
            write ( fd, MESSAGE, sizeof ( MESSAGE ) );
            close ( fd );
         }
      }
   }

   //Note the first child will never exit as it is a blocking shell script
   while ( ( wpid = wait ( &status ) ) > 0 )
   {
      printf ( "Exit status of %d was %d (%s)\n", ( int ) wpid, status, ( status == 0 ) ? "accept" : "reject" );
      fflush(stdout);
   }

   unlink ( PIPEPATH );

   return 0;

As I pointed out, the program will never exit, so just issue a Ctrl+C }

Edit 3:

Ok I am making progress, it seems that what you give to the pipe and what you get back are not the same thing, run this script and observe:

#include <stdio.h>
#include <fcntl.h>
#define PIPE_PATH "testpipe"

int main( int argc, char *argv[] ) {
   int fd;
   FILE *fp;
   char c;

   if ( atoi ( argv [ 1 ] ) == 1 )
   {
      printf ("Writer [%s]\n", argv[1]);

      mkfifo ( PIPE_PATH, 0666 );

      fd = open ( PIPE_PATH, O_WRONLY );
      c = getchar();
      write(fd, c, 1);

      close(fd);
   }
   else if ( atoi ( argv [ 1 ] ) == 2 )
   {
      printf ( "Reader [%s]\n", argv[1] );

      fp = fopen( PIPE_PATH, "r" );
      c = getc ( fp );
      putchar ( c );

      printf ( "\n" );
      fclose ( fp );

      unlink( PIPE_PATH );
   }

   return 0;
}

EDIT 4:

JF Sebastian asked some good questions, this is in response to those questions. What I am ultimately trying to do is to synchronize 2 or more instances of omxplayer playing the same movie on 2 or more raspberry pis. omxplayer-sync tries to achieve this, but it is not accurate, it is written in Python which is not suitable for this, and its approach is, IMO, not a good one. I am working my way up the food chain trying out the easiest solutions to see if they are viable. From easiest to hardest, here is what I have tried so far (or plan on trying)

  1. Launch omxplayer instances from the shell at an agreed upon time in future at the same time: FAIL
  2. Launch omxplayer instances from a Python script (more accurate time wise) at an agreed upon time in future at the same time: FAIL
  3. Launch omxplayer instances from a C program (even more accurate time wise) at an agreed upon time in future at the same time: FAIL
  4. Launch and immediately pause, then unpause omxplayer instances from a Python script (more accurate time wise) at an agreed upon time in future at the same time: FAIL
  5. Launch and immediately pause, then unpause (via echo -np > namedpipe ) omxplayer instances from a C program (more accurate time wise) at an agreed upon time in future at the same time: FAIL
  6. Launch and immediately pause, then unpause (via write ( fd_of_named_pipe, 'p', sizeof ( 'p') ); ) omxplayer instances from a C program (more accurate time wise) at an agreed upon time in future at the same time: PENDING
  7. Reimplement omxplayer and modulate play speeds to catch up with fastest player: FUTURE

Basically before investing a huge chunk of my life into understanding and then modifying the source code of omxplayer (7) I want to see if using C's native write ( fd_of_named_pipe, 'p', sizeof ( 'p') ) operation is faster than its system(echo -np > namedpipe) call which forks a child and calls the shell to write to the named pipe (my hunch tells me it will be much faster, and hopefully more accurate). If this works and unpauses all instances to within 15ms, fantastic, I won't have to look at omxpleyer 's source code ever. If not, as a last resort I will start modifying the source code.

It might be useful to use a program which is made for reading stdin.

echo does not do so, but it prints its command line arguments. That is a difference.

Try cat .

You could use

xargs < testpipe echo

This will pass the output of testpipe (one line at a time) as an argument to echo - and produces

We are not alone

to the screen...

Note - your comment "I want to modify the C code to work with echo", without considering how what echo coes (in particular, that it doesn't interact with pipes), seems backwards. Your real question should be "how do I get commands from C into omxplayer"?

I found the following code snippet for omxplayer control here

mkfifo /tmp/cmd

omxplayer -ohdmi mymedia.avi < /tmp/cmd

echo . > /tmp/cmd (Start omxplayer running as the command will initial wait for input via the fifo)

echo -n p > /tmp/cmd - Playback is paused

echo -n q > /tmp/cmd - Playback quits

This suggests to me that the following should work for you:

omxplayer -ohdmi mymedia.avi < testpipe

Where your C program is producing characters as needed to feed omxplayer . Just don't close the pipe, keep the C program running. However, when I tried writing a little test program to confirm this:

#include <stdio.h>
#include <fcntl.h>
#define PATH "testpipe"

int main(void) {
    int fd;
    char c;

    mkfifo(PATH, 0666);
    fd = open( PATH, O_WRONLY);
    char keyStroke[2] = " ";

    while((c = getchar())!='q') {
        keyStroke[0] = c;
        write(fd, keyStroke, 1);
    }

    close(fd);
    unlink(PATH);
    return 0;
}

I found the above did not get any response until I hit 'q' at the sending terminal - in other words, until the pipe was closed. A little searching go me to https://stackoverflow.com/a/4060641/1967396 where they suggested a lower level "pipe reading program", so I implemented that:

#include <stdio.h>
#define PIPE "testpipe"

int main(void) {
    FILE *fp;
    fp = fopen(PIPE, "r");
    int c;

    while((c = getc(fp)) != EOF)
    {
        printf("%c", c);
    }

    fclose(fp);
    return 0;
}

Running this in one terminal, and the earlier program in another terminal, I could now see the characters from one terminal appear in the other one. And this, I think, is what you wanted (although I still had to press "enter" after each character at the input terminal because I was using getchar() , but I am sure you can figure out how to fix that).

Let me know if this is helpful...

Here's how you could emulate { sleep 10; echo -np ; } | omxplayer { sleep 10; echo -np ; } | omxplayer { sleep 10; echo -np ; } | omxplayer command:

/* for clock_nanosleep() */
#if ! defined(_POSIX_C_SOURCE)  || _POSIX_C_SOURCE < 200112L
#define _POSIX_C_SOURCE 200112L
#endif

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <unistd.h>

#define Close(FD) do {                                          \
    const int Close_fd = (FD);                                  \
    if (close(Close_fd) == -1)                                  \
      fprintf(stderr, "%s:%d: close(" #FD ") %d: %s\n",         \
              __FILE__, __LINE__, Close_fd, strerror(errno));   \
  }while(0)

static void _report_error_and_exit(const char* msg) {
  perror(msg);
  _exit(EXIT_FAILURE);
}

static void report_error_and_exit(const char* msg) {
  perror(msg);
  exit(EXIT_FAILURE);
}

int main(void) {
  int fd[2]; /* pipe */
  pid_t pid = -1;

  if (pipe(fd) == -1)
    report_error_and_exit("pipe");

  if ((pid = fork()) == -1)
    report_error_and_exit("fork");
  else if (pid == 0)  { /* child: sleep, write */
    Close(fd[0]); /* close unused read end of the pipe */

    { /* sleep until specified time */
      struct timespec endtime = {0};
      int r = -1;

      /* set some time into the future (just as an example) */
      if (clock_gettime(CLOCK_REALTIME, &endtime) < 0)
        _report_error_and_exit("clock_gettime");
      endtime.tv_sec += 10; /* seconds */

      /* NOTE: use settable system-wide real-time clock */
      while ((r = clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME,
                                  &endtime, NULL)) == EINTR)
        ; /* retry */

      if (r != 0)
        _report_error_and_exit("clock_nanosleep");
    }

    { /* write to the pipe */
      char c = 'p';
      ssize_t size = sizeof c, n = size, m = 0;
      while (n > 0) { /* until there is something to write */
        if ((m = write(fd[1], &c + (size - n), n)) > 0)
          n -= m;
        else if (errno != EINTR)
          _report_error_and_exit("write");
      }
    }
    _exit(EXIT_SUCCESS); /* child done */
  }

  /* parent: run `omxplayer < fd[0]` */
  Close(fd[1]); /* close unused write end of the pipe */

  /* redirect stdin */
  if (fd[0] != STDIN_FILENO) {
    if (dup2(fd[0], STDIN_FILENO) == -1)
      report_error_and_exit("dup2");
    else
      Close(fd[0]);
  }
  execlp("omxplayer", "omxplayer", NULL);
  _report_error_and_exit("execlp");
}

To try it, run:

$ gcc *.c -lrt && ./a.out

It should start omxplayer immediately and write to it p at current time plus 10 seconds. It is written to use an absolute time for the sleep.

It should help you to tick the next item on your omxplayer-sync list. But it won't solve the issue of synchronizing playback on multiple video players on different hosts.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM