To simplify Thomas Dickey's and Chris Dodd's answers, the typical code to select which descriptor is used to refer to the terminal is
int ttyfd;
/* Check standard error, output, and input, in that order. */
if (isatty(fileno(stderr)))
ttyfd = fileno(stderr);
else
if (isatty(fileno(stdout)))
ttyfd = fileno(stdout);
else
if (isatty(fileno(stdin)))
ttyfd = fileno(stdin);
else
ttyfd = -1; /* No terminal; redirecting to/from files. */
If your application insists on having access to the controlling terminal (the terminal the user used to execute this process), if there is one, you can use the following new_terminal_descriptor()
function. For simplicity, I'll embed it in an example program:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
int new_terminal_descriptor(void)
{
/* Technically, the size of this buffer should be
* MAX( L_ctermid + 1, sysconf(_SC_TTY_NAME_MAX) )
* but 256 is a safe size in practice. */
char buffer[256], *path;
int fd;
if (isatty(fileno(stderr)))
if (!ttyname_r(fileno(stderr), buffer, sizeof buffer)) {
do {
fd = open(path, O_RDWR | O_NOCTTY);
} while (fd == -1 && errno == EINTR);
if (fd != -1)
return fd;
}
if (isatty(fileno(stdout)))
if (!ttyname_r(fileno(stdout), buffer, sizeof buffer)) {
do {
fd = open(path, O_RDWR | O_NOCTTY);
} while (fd == -1 && errno == EINTR);
if (fd != -1)
return fd;
}
if (isatty(fileno(stdin)))
if (!ttyname_r(fileno(stdin), buffer, sizeof buffer)) {
do {
fd = open(path, O_RDWR | O_NOCTTY);
} while (fd == -1 && errno == EINTR);
if (fd != -1)
return fd;
}
buffer[0] = '\0';
path = ctermid(buffer);
if (path && *path) {
do {
fd = open(path, O_RDWR | O_NOCTTY);
} while (fd == -1 && errno == EINTR);
if (fd != -1)
return fd;
}
/* No terminal. */
errno = ENOTTY;
return -1;
}
static void wrstr(const int fd, const char *const msg)
{
const char *p = msg;
const char *const q = msg + ((msg) ? strlen(msg) : 0);
while (p < q) {
ssize_t n = write(fd, p, (size_t)(q - p));
if (n > (ssize_t)0)
p += n;
else
if (n != (ssize_t)-1)
return;
else
if (errno != EINTR)
return;
}
}
int main(void)
{
int ttyfd;
ttyfd = new_terminal_descriptor();
if (ttyfd == -1)
return EXIT_FAILURE;
/* Let's close the standard streams,
* just to show we're not using them
* for anything anymore. */
fclose(stdin);
fclose(stdout);
fclose(stderr);
/* Print a hello message directly to the terminal. */
wrstr(ttyfd, "\033[1;32mHello!\033[0m\n");
return EXIT_SUCCESS;
}
The wrstr()
function is just a helper function, that immediately writes the specified string to the specified file descriptor, without buffering. The string contains ANSI color codes, so that if successful, it will print a light green Hello!
to the terminal, even if the standard streams were closed.
If you save the above as example.c
, you can compile it using e.g.
gcc -Wall -Wextra -O2 example.c -o example
and run using
./example
Because the new_terminal_descriptor()
uses ctermid()
function to obtain the name (path) to the controlling terminal as a last resort -- this is not common, but I wanted to show here it is easy to do if you decide it is necessary --, it will print the hello message to the terminal even when all streams are redirected:
./example </dev/null >/dev/null 2>/dev/null
Finally, in case you are wondering, none of this is "special". I am not talking about the console terminal, which is the text-based console interface many Linux distributions provide as an alternative to the graphical environment, and the only local interface most Linux servers provide. All of the above uses just normal POSIX pseudoterminal interfaces, and will work just fine using e.g. xterm
or any other normal terminal emulator (or Linux console), in all POSIXy systems -- Linux, Mac OS X, and the BSD variants.