通过tcsetattr(fd.....)设置终端属性时,fd可以是stdout还是stdin?
When setting terminal attributes via tcsetattr(fd.....), can fd be either stdout or stdin?
我一直在寻找 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
一起。
通过实验,我找到了以下答案:
三元组 stderr
、stdout
、stdin
中的每一个都可以用于通过 tcsetattr(fd....)
函数更改终端设置。一旦更改生效,读取 tcgsetattr(stdin....)
、tcgsetattr(stdout....)
,并且 tcgsetattr(sterr....)
在 struct termios.h
中返回相同的内容,可以通过 memcmp(&termios_stdin,&termios_stdout,sizeof(struct termios)) == 0
[=21= 验证]
也可能手册页间接说明了这一点
tcgetattr() gets the parameters associated with the object referred by
fd and stores them in the termios structure referenced by termios_p.
This function may be invoked from a background process; however, the
terminal attributes may be subsequently changed by a foreground
process.
about fd
因此 fd 引用的对象总是同一个终端
fd
代表文件描述符,它是对OS文件对象的引用。因为是引用,多个不同的文件描述符可能引用同一个文件对象。
stdin
、stdout
和 stderr
是 FILE *
对象——指向 stdio FILE
数据结构的实际指针。您可以使用 fileno
函数获取引用底层 OS 对象的文件描述符。
所以这里有两个间接级别。 FILE *
可以指代同一个 FILE
,但它们不是; stdin
、stdout
和 stderr
有 3 个单独的 FILE
对象。这些 FILE
对象每个都包含一个文件描述符,通常是 0、1 和 2(我说的是正常情况——OS/lib 以这种方式设置它们,只有在您的程序中明确更改它们时它们才会更改). 3 个文件描述符通常都指向相同的底层 OS 对象,这是一个终端对象。
因为(通常)只有一个终端,并且所有这些文件描述符(通常)都引用它,所以使用哪个 fd(0、1 或 2)作为 [= 的第一个参数并不重要24=].
请注意, 这些 fd
可能引用不同的对象 -- 如果您使用重定向(<
或 >
在 shell) 中,那么其中一个或多个将引用其他文件对象而不是终端。
同意@chris-dodd 的文件描述符对应于流 stdin、stdout 和 stderr通常指的是同一个终端,原题需要加点:
fd
tcgetattr
and tcsetattr
的 fd
参数( 文件描述符 )必须是 终端 。
- 您可以使用
fileno
为流获取此信息,例如 fileno(stdin)
。
- POSIX defines constants for the default assignment of file descriptors to stdin, stdout and stderr as
STDIN_FILENO
, STDOUT_FILENO
and STDERR_FILENO
. However, it is possible to reopen any of the streams (or use dup
or dup2
) 并更改实际的文件描述符。
- 虽然您可以获得流的文件描述符,但如果您正在对终端属性做任何有趣的事情,这可能会干扰用于流的缓冲。如果您必须混合使用两者(文件描述符和流),请在读取或写入流之前更改终端属性。
- 您还可以在终端设备上使用
open
获取文件描述符。如果流被重定向,并且您的应用程序必须与终端一起工作,这将很有用。密码提示执行此操作。
- 可以从程序中读取终端设备
tty
(即使stdin等被重定向)
- 程序可以使用
isatty
检查 文件描述符 以查看它是否是终端。如果流被重定向到文件或管道,则它不是终端。
进一步阅读:
- 11. General Terminal Interface (POSIX)
- What is the difference between stdin and STDIN_FILENO?
为了简化 and 答案,select 用于引用终端的描述符的典型代码是
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] = '[=11=]';
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, "3[1;32mHello!3[0m\n");
return EXIT_SUCCESS;
}
wrstr()
函数只是一个辅助函数,它会立即将指定的字符串写入指定的文件描述符,无需缓冲。该字符串包含 ANSI 颜色代码,因此如果成功,它将向终端打印浅绿色 Hello!
,即使标准流已关闭。
如果你把上面的保存为example.c
,你可以使用例如
编译它
gcc -Wall -Wextra -O2 example.c -o example
和运行使用
./example
因为 new_terminal_descriptor()
使用 ctermid()
函数来获取控制终端的名称(路径)作为最后的手段——这并不常见,但我想在这里展示它很容易如果您认为有必要,它会向终端打印问候消息,即使所有流都被重定向:
./example </dev/null >/dev/null 2>/dev/null
最后,如果您想知道,none 是 "special"。我不是在谈论 控制台终端 ,它是许多 Linux 发行版提供的基于文本的控制台界面,作为图形环境的替代方案,也是唯一的本地界面大多数 Linux服务器提供。以上所有仅使用普通的 POSIX 伪终端接口,并且可以正常使用,例如xterm
或任何其他普通终端仿真器(或 Linux 控制台),在所有 POSIXy 系统中 -- Linux、Mac OS X和 BSD 变体。
我一直在寻找 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
一起。
通过实验,我找到了以下答案:
三元组 stderr
、stdout
、stdin
中的每一个都可以用于通过 tcsetattr(fd....)
函数更改终端设置。一旦更改生效,读取 tcgsetattr(stdin....)
、tcgsetattr(stdout....)
,并且 tcgsetattr(sterr....)
在 struct termios.h
中返回相同的内容,可以通过 memcmp(&termios_stdin,&termios_stdout,sizeof(struct termios)) == 0
[=21= 验证]
也可能手册页间接说明了这一点
tcgetattr() gets the parameters associated with the object referred by fd and stores them in the termios structure referenced by termios_p. This function may be invoked from a background process; however, the terminal attributes may be subsequently changed by a foreground process.
about fd
因此 fd 引用的对象总是同一个终端
fd
代表文件描述符,它是对OS文件对象的引用。因为是引用,多个不同的文件描述符可能引用同一个文件对象。
stdin
、stdout
和 stderr
是 FILE *
对象——指向 stdio FILE
数据结构的实际指针。您可以使用 fileno
函数获取引用底层 OS 对象的文件描述符。
所以这里有两个间接级别。 FILE *
可以指代同一个 FILE
,但它们不是; stdin
、stdout
和 stderr
有 3 个单独的 FILE
对象。这些 FILE
对象每个都包含一个文件描述符,通常是 0、1 和 2(我说的是正常情况——OS/lib 以这种方式设置它们,只有在您的程序中明确更改它们时它们才会更改). 3 个文件描述符通常都指向相同的底层 OS 对象,这是一个终端对象。
因为(通常)只有一个终端,并且所有这些文件描述符(通常)都引用它,所以使用哪个 fd(0、1 或 2)作为 [= 的第一个参数并不重要24=].
请注意, 这些 fd
可能引用不同的对象 -- 如果您使用重定向(<
或 >
在 shell) 中,那么其中一个或多个将引用其他文件对象而不是终端。
同意@chris-dodd 的文件描述符对应于流 stdin、stdout 和 stderr通常指的是同一个终端,原题需要加点:
fd
tcgetattr
andtcsetattr
的fd
参数( 文件描述符 )必须是 终端 。- 您可以使用
fileno
为流获取此信息,例如fileno(stdin)
。 - POSIX defines constants for the default assignment of file descriptors to stdin, stdout and stderr as
STDIN_FILENO
,STDOUT_FILENO
andSTDERR_FILENO
. However, it is possible to reopen any of the streams (or usedup
ordup2
) 并更改实际的文件描述符。 - 虽然您可以获得流的文件描述符,但如果您正在对终端属性做任何有趣的事情,这可能会干扰用于流的缓冲。如果您必须混合使用两者(文件描述符和流),请在读取或写入流之前更改终端属性。
- 您还可以在终端设备上使用
open
获取文件描述符。如果流被重定向,并且您的应用程序必须与终端一起工作,这将很有用。密码提示执行此操作。 - 可以从程序中读取终端设备
tty
(即使stdin等被重定向) - 程序可以使用
isatty
检查 文件描述符 以查看它是否是终端。如果流被重定向到文件或管道,则它不是终端。
进一步阅读:
- 11. General Terminal Interface (POSIX)
- What is the difference between stdin and STDIN_FILENO?
为了简化
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] = '[=11=]';
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, "3[1;32mHello!3[0m\n");
return EXIT_SUCCESS;
}
wrstr()
函数只是一个辅助函数,它会立即将指定的字符串写入指定的文件描述符,无需缓冲。该字符串包含 ANSI 颜色代码,因此如果成功,它将向终端打印浅绿色 Hello!
,即使标准流已关闭。
如果你把上面的保存为example.c
,你可以使用例如
gcc -Wall -Wextra -O2 example.c -o example
和运行使用
./example
因为 new_terminal_descriptor()
使用 ctermid()
函数来获取控制终端的名称(路径)作为最后的手段——这并不常见,但我想在这里展示它很容易如果您认为有必要,它会向终端打印问候消息,即使所有流都被重定向:
./example </dev/null >/dev/null 2>/dev/null
最后,如果您想知道,none 是 "special"。我不是在谈论 控制台终端 ,它是许多 Linux 发行版提供的基于文本的控制台界面,作为图形环境的替代方案,也是唯一的本地界面大多数 Linux服务器提供。以上所有仅使用普通的 POSIX 伪终端接口,并且可以正常使用,例如xterm
或任何其他普通终端仿真器(或 Linux 控制台),在所有 POSIXy 系统中 -- Linux、Mac OS X和 BSD 变体。