[英]When setting terminal attributes via tcsetattr(fd…), can fd be either stdout or stdin?
我一直在尋找男人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
。
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)作為tcsetaddr
的第一個參數tcsetaddr
。
請注意, 有可能這些fd
s到引用不同的對象-如果你開始重定向程序( <
或>
在shell),那么一個或多個的他們會參考一些其他的文件對象,而不是終端。
為了簡化Thomas Dickey和Chris Dodd的答案,選擇哪個描述符用於引用終端的典型代碼是
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
,則可以使用eg編譯它
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偽終端接口,並且在所有POSIXy系統(Linux,Mac OS X和BSD變體)中使用例如xterm
或任何其他普通終端仿真器(或Linux控制台)都可以正常工作。
同意@ chris-dodd對應於流stdin , stdout和stderr的文件描述符通常引用同一個終端,原始問題需要一些點:
tcgetattr
和tcsetattr
的fd
參數( 文件描述符 )必須是終端 。 fileno
獲取此流,例如fileno(stdin)
。 STDIN_FILENO
, STDOUT_FILENO
和STDERR_FILENO
。 但是,可以重新打開任何流(或使用dup
或dup2
)並更改實際的文件描述符。 open
來獲取文件描述符。 如果重定向流,並且您的應用程序必須使用終端,這將非常有用。 密碼提示執行此操作。 tty
讀取終端設備(即使重定向stdin等)。 isatty
檢查文件描述符 ,看它是否是終端。 如果將流重定向到文件或管道,則它不是終端。 進一步閱讀:
通過實驗,我發現自己有以下答案:
每個三重stderr
, stdout
, stdin
都可以通過tcsetattr(fd....)
函數用於更改終端設置。 一旦改變完成,讀取tcgsetattr(stdin....)
, tcgsetattr(stdout....)
,以及tcgsetattr(sterr....)
返回struct termios.h
的相同內容,可以通過memcmp(&termios_stdin,&termios_stdout,sizeof(struct termios)) == 0
驗證memcmp(&termios_stdin,&termios_stdout,sizeof(struct termios)) == 0
也許這個手冊頁有點間接地說明了這一點
tcgetattr()獲取與fd引用的對象關聯的參數,並將它們存儲在termios_p引用的termios結構中。 可以從后台進程調用此函數; 然而,終端屬性可以隨后由前台進程改變。
關於fd
因此fd引用的對象總是相同的終端
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.