我一直在寻找man 3 tcgetattr(因为我想更改程序中的终端设置)并找到了这个。
int tcgetattr(int fd, struct termios *termios_p);
int tcsetattr(int fd, int optional_actions,
const struct termios *termios_p);
我想知道fd
应该是什么意思? (似乎是stdin
,但我不明白为什么)?
我的理解是终端是输入和输出,因为我的理解是/dev/tty
或/dev/pty
一起产生stdin
,stdout
和stderr
。
答案 0 :(得分:4)
fd
代表文件描述符,它是对OS文件对象的引用。因为它是一个引用,所以多个不同的文件描述符可能引用同一个文件对象。
stdin
,stdout
和stderr
是FILE *
个对象 - 指向stdio FILE
数据结构的实际指针。您可以使用fileno
函数获取引用底层OS对象的文件描述符。
所以这里有两个间接层次。 FILE *
可以全部引用相同的FILE
,但他们不会; FILE
,stdin
和stdout
有3个单独的stderr
个对象。这些FILE
对象每个都包含一个文件描述符,通常为0,1和2(我通常说 - OS / lib以这种方式设置它们,只有在程序中明确更改它们时它们才会改变) )。然后,3个文件描述符通常都将引用相同的底层OS对象,即单个终端对象。
由于(通常)只有一个终端,并且所有这些文件描述符(通常)都引用它,因此使用哪个fd(0,1或2)并不重要tcsetaddr
的论据。
请注意,这些fd
可以 来引用不同的对象 - 如果您使用重定向(<
或>
启动您的程序shell)然后其中一个或多个将引用其他文件对象而不是终端。
答案 1 :(得分:2)
为了简化Thomas Dickey's和Chris Dodd's个答案,选择使用哪个描述符来引用终端的典型代码是
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. */
如果您的应用程序坚持访问控制终端(用户用于执行此过程的终端),如果有,则可以使用以下new_terminal_descriptor()
功能。为简单起见,我将其嵌入到示例程序中:
#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;
}
wrstr()
函数只是一个辅助函数,它立即将指定的字符串写入指定的文件描述符,而不进行缓冲。该字符串包含ANSI颜色代码,因此如果成功,它将向终端打印浅绿色Hello!
,即使标准流已关闭。
如果将上述内容保存为example.c
,则可以使用例如
gcc -Wall -Wextra -O2 example.c -o example
并使用
运行./example
因为new_terminal_descriptor()
使用ctermid()
函数来获取控制终端的名称(路径)作为最后的手段 - 这不常见,但我想在这里展示它很容易做到如果您认为有必要 - 即使所有流都被重定向,它也会向终端打印hello消息:
./example </dev/null >/dev/null 2>/dev/null
最后,如果你想知道,这一切都不是“特别的”。我不是在谈论控制台终端,它是许多Linux发行版提供的基于文本的控制台界面,作为图形环境的替代,以及大多数Linux服务器提供的唯一本地接口。所有上述内容仅使用普通的POSIX伪终端接口,并且可以使用例如正常的POSIX伪终端接口。所有POSIXy系统中的xterm
或任何其他普通终端仿真器(或Linux控制台) - Linux,Mac OS X和BSD变体。
答案 2 :(得分:1)
同意@ chris-dodd对应于流 stdin , stdout 和 stderr 的文件描述符通常是指同一个终端,原始问题需要一些要点:
tcgetattr
的fd
参数(文件描述符)和tcsetattr
必须是终端。fileno
获取此流,例如fileno(stdin)
。STDIN_FILENO
,{ {1}}和STDOUT_FILENO
。但是,可以reopen任何流(或使用dup
或dup2
)并更改实际的文件描述符。STDERR_FILENO
获取文件描述符。如果重定向流,并且您的应用程序必须使用终端,这将非常有用。密码提示执行此操作。tty
中读取终端设备(即使重定向 stdin 等)。isatty
检查文件描述符以查看它是否是终端。如果将流重定向到文件或管道,则它不是终端。进一步阅读:
答案 3 :(得分:0)
通过实验,我发现自己有以下答案:
三个0
,stderr
,stdout
中的每一个都可用于通过stdin
功能更改终端设置。完成更改后,阅读tcsetattr(fd....)
,tcgsetattr(stdin....)
以及tcgsetattr(stdout....)
返回tcgsetattr(sterr....)
中可通过struct termios.h
验证的相同内容
也许这个手册页有点间接地说明了这个
tcgetattr()获取与引用的对象关联的参数 fd并将它们存储在termios_p引用的termios结构中。 可以从后台进程调用此函数;然而 随后可以通过前景改变终端属性 过程
约memcmp(&termios_stdin,&termios_stdout,sizeof(struct termios)) == 0
因此fd引用的对象始终是相同的终端