Test if stdin has input for C++ (windows and/or linux)
Asked Answered
H

5

17

I basically want to test if stdin has input (like if you echo and pipe it). I have found solutions that work, but they are ugly, and I like my solutions to be clean.

On linux I use this:

bool StdinOpen() {
  FILE* handle = popen("test -p /dev/stdin", "r");
  return pclose(handle) == 0;
}

I know that I should add more error handling, but it's besides the point.

On windows I use this:

bool StdinOpen() {
  static HANDLE handle = GetStdHandle(STD_INPUT_HANDLE);
  DWORD bytes_left;
  PeekNamedPipe(handle, NULL, 0, NULL, &bytes_left, NULL);
  return bytes_left;
}

That is fine for linux, but I want to know what are the equivalent APIs that I can call without using a pipe (like for test -f $file you do fopen($file, "r") != NULL). I have an inkling that I could open("/dev/stdin", "r") and do the same thing, but I want to know the best way to do it.

Summary: I want to know the APIs I could use to substitute for test -p /dev/stdin for linux, and, if you know a better solution for windows.

Hemihydrate answered 27/7, 2011 at 4:42 Comment(5)
Your PeekNamedPipe solution fails if standard input is a file handle (rather than a pipe). Also, your handle variable should not be static. If the handle gets redirected while your app runs you're going to surprise yourself later.Wineglass
@Billy: I don't think a handle could get randomly redirected. Sure, you might change what you consider to be stdin, but the old handle is still there. But I agree about the first part.Xenocryst
Lionel B provides some code for Linux at bytes.com/topic/c/answers/841283-how-make-non-blocking-call-cin - the discussion's worth reading too.Cleveite
I could test if stdin is a pipe using DWORD dw; !GetConsoleMode(handle, &dw) and use the current method, and otherwise use _kbhit() != 0. Thoughts?Hemihydrate
Similar question with (IMO) better answers: #19956117Usurer
O
15

Here's a solution for POSIX (Linux): I'm not sure what's the equivalent of poll() on Windows. On Unix, The file descriptor with number 0 is the standard input.

#include <stdio.h>
#include <sys/poll.h>

int main(void)
{
        struct pollfd fds;
        int ret;
        fds.fd = 0; /* this is STDIN */
        fds.events = POLLIN;
        ret = poll(&fds, 1, 0);
        if(ret == 1)
                printf("Yep\n");
        else if(ret == 0)
                printf("No\n");
        else
                printf("Error\n");
        return 0;
}

Testing:

$ ./stdin
No
$ echo "foo" | ./stdin
Yep
October answered 27/7, 2011 at 4:54 Comment(6)
This is exactly what I wanted. Now, I would be eternally grateful if you could tell me how to measure the input buffer size, but I don't want to sound like a question hog.Hemihydrate
You can use read() to get the input. First set the file descriptor 0 to non-blocking using fcntl(), then read() will return the number of bytes read or 0 when there is no more incoming data.October
+1. Another commonly-used alternative is select() - conceptually similar usage. It's crucial to note that these ask the operating system if there's new data available from the descriptor - if you're operating at that level, you must use a read() directly on the descriptor too, you can't use library-level stdin streams or std::cin (unless you provide a new buffer implementation).Cleveite
On my platform I can use fds.fd = fileno(stdin) instead of fds.fd = 0. This will improve platform independence if it is needed, not even sure though that there is a platform where stdin's fd is non-zero. I'm using an ARM Linux distro with a 3.2 kernel.Biotype
Yes, it's certainly at least more readable. POSIX defines stdin fileno to be 0 - see stdin(3p) - and there's also a define STDIN_FILENO with value 0 defined at stdio.h.October
One should rather test the output revent after calling poll(...), like fds.events == fds.reventsFawcette
T
5

Would this not work?

std::cin.rdbuf()->in_avail();
Tonitonia answered 22/3, 2016 at 18:5 Comment(1)
Unfortunately it does NOT (at least on Windows). It always returns 0 unless you do a blocking call like peek() to invoke the console input APIs, which defeats the whole purpose.Unhitch
X
1

I'm not sure, but does _kbhit() do what you need?

Xenocryst answered 27/7, 2011 at 4:49 Comment(1)
It only works for actual keyboard input in a console app. Not for redirected input!Unhitch
U
1

Windows:

return ::WaitForSingleObject(::GetStdHandle(STD_INPUT_HANDLE), 3) == WAIT_OBJECT_0;

Old question, but none of the (current) other answers work on Windows (and I might be looking it up again in an year :).

Unhitch answered 1/6, 2023 at 23:32 Comment(0)
T
1

[ POSIX ] ioctl() is pretty straightforward and common since it's part of the standard C POSIX library, https://man7.org/linux/man-pages/man2/ioctl.2.html
Syntax: ioctl(int fd, int opt, int *n)
In this case ioctl(0, FIONREAD, &available_bytes);
0 -> stdin
FIONREAD -> opt to ask for available bytes
&available_bytes -> pointer to the int bytes variable to store the available bytes.

#include <sys/ioctl.h>
#include <stdio.h>

int stdin_avail() {
  int bytes = 0;
  if(ioctl(0,FIONREAD, &bytes) == -1){
    return -1;
  };
  return bytes;
}

int main(int argc, const char **args)
{
  int available_bytes = stdin_avail();
  printf("available: %i bytes\n", available_bytes);
  return 0;
}
Tried answered 12/10, 2023 at 15:21 Comment(2)
I had [LINUX] at the beginning of the answer + the guy was looking for new ways on both Windows and Linux. Also I didn't mean the standard C library, I corrected it. Let me know if I'm still wrong,Drover
FIONREAD sets int, not uint-64. By using uint64 you likely read 8 bytes to much. If these are zero it looks OK ~ but you are actually reading uninitialised data. One should also always check if it returns success. For FIONREAD that is zero. man7.org/linux/man-pages/man2/ioctl_tty.2.htmlVarian

© 2022 - 2025 — McMap. All rights reserved.