Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pipe data to a program which calls scanf() and read() in Linux

Tags:

c

linux

unix

pipe

I have a C program which looks like this:

#include <stdio.h>
#include <unistd.h>

int main()
{
    int n;
    char str[16];
    scanf("%d", &n);
    printf("n: %d\n", n);

    int count = read(STDIN_FILENO, str, 16);
    printf("str: %s\n", str);
    printf("read %d bytes\n", count);
}

If I pipe data into this program using a command like

(echo -en '45\n'; echo -en 'text\n') | ./program

only scanf() actually reads data. read() simply reads 0 bytes.

In other words, the program outputs

n: 45
str:
read 0 bytes

How can I pipe data to both scanf() and read()?

Edit: Sorry, I should have clarified: I'm looking for a way to do this without modifying the source. Thanks to the people who answered and commented anyway.

like image 211
IGNUcius Avatar asked Nov 02 '14 08:11

IGNUcius


People also ask

How do you read data from a pipe?

When a user process attempts to read from an empty pipe (or FIFO), the following happens: If one end of the pipe is closed, 0 is returned, indicating the end of the file. If the write side of the FIFO has closed, read(2) returns 0 to indicate the end of the file.

Can we use scanf and gets in same program?

Do (f)gets and sscanf from that? @PeterSchneider there's nothing that you can do with scanf() and not with fgets() . The only difference is that scanf() is hard to use correctly and it's hazardous.

How does pipe () work?

pipe() is a system call that facilitates inter-process communication. It opens a pipe, which is an area of main memory that is treated as a "virtual file". The pipe can be used by the creating process, as well as all its child processes, for reading and writing.

What does pipe () do in C?

pipe() is a Linux system function. The pipe() system function is used to open file descriptors, which are used to communicate between different Linux processes. In short, the pipe() function is used for inter-process communication in Linux.


2 Answers

#include <stdio.h>
#include <unistd.h>

int main()
{
    int n;
    char str[16];
    setbuf(stdin, NULL); // Ensure that there's no buffering for stdin
    scanf("%d", &n);
    printf("n: %d\n", n);

    int count = read(STDIN_FILENO, str, 16);
    printf("str: %s\n", str);
    printf("read %d bytes\n", count);
}

As the previous answers said, scanf is causing your problem because it uses a buffer. So you can make sure it doesn't by callingsetbuf(stdin,NULL).

like image 157
4Mechanix Avatar answered Oct 22 '22 22:10

4Mechanix


It is not recommended at all to use both file descriptor functions (read) and stream functions (scanf) for the same file descriptor. Functions using FILE * (i.e. fread/fprintf/scanf/...) are buffering data while functions using file descriptors (i.e. read/write/...) do not use those buffers. In your case, the easiest way to fix the program is to use fread instead of read. The program may look like:

#include <stdio.h>
#include <unistd.h>

int main() {
  int n;
  char str[16];
  scanf("%d", &n);
  printf("n: %d\n", n);

  int count = fread(str, 16, 1, stdin);
  printf("str: %s\n", str);
  printf("read %d bytes\n", count);
}

In your original example, scanf has read the input ahead and stored it in its buffer. Because the input was so short and was available immediately it was entirely read to the buffer and your invocation of read had nothing more to read.

This does not happen when you enter the input directly from a terminal, because scanf does not buffer data beyond one line when reading from a terminal device. This would also not happen if you create a time pause in your piped input for example by a command:

(echo -en '45\n'; sleep 1; echo -en 'text\n') | ./program
like image 34
Marian Avatar answered Oct 22 '22 20:10

Marian