簡體   English   中英

通過tcsetattr(fd ...)設置終端屬性時,fd可以是stdout還是stdin?

[英]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產生stdinstdoutstderr

fd代表文件描述符,它是對OS文件對象的引用。 因為它是引用,所以多個不同的文件描述符可以引用相同的文件對象。

stdinstdoutstderrFILE *對象 - stdio FILE結構的實際指針。 您可以使用fileno函數獲取引用底層OS對象的文件描述符。

所以這里有兩個間接層次。 FILE *都可以引用相同的FILE ,但它們不是; stdinstdoutstderr有3個單獨的FILE對象。 這些FILE對象每個都包含一個文件描述符,通常為0,1和2(我通常說 - OS / lib以這種方式設置它們,只有在程序中明確更改它們時它們才會改變)。 然后,3個文件描述符通常都將引用相同的底層OS對象,即單個終端對象。

由於(通常)只有一個終端,並且所有這些文件描述符(通常)都引用它,因此使用哪個fd(0,1或2)作為tcsetaddr的第一個參數tcsetaddr

請注意, 可能這些fd s到引用不同的對象-如果你開始重定向程序( <>在shell),那么一個或多個的他們會參考一些其他的文件對象,而不是終端。

為了簡化Thomas DickeyChris 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對應於流stdinstdoutstderr的文件描述符通常引用同一個終端,原始問題需要一些點:

  • tcgetattrtcsetattrfd參數( 文件描述符 )必須是終端
  • 您可以使用fileno獲取此流,例如fileno(stdin)
  • POSIX將文件描述符的默認賦值定義stdinstdoutstderrSTDIN_FILENOSTDOUT_FILENOSTDERR_FILENO 但是,可以重新打開任何流(或使用dupdup2 )並更改實際的文件描述符。
  • 雖然您可以獲取流的文件描述符,但如果您正在對終端屬性執行任何有趣的操作,則可能會干擾用於流的緩沖。 如果必須混合使用兩者(文件描述符和流),請讀取或寫入流之前對終端屬性進行更改。
  • 您還可以使用終端設備上的open來獲取文件描述符。 如果重定向流,並且您的應用程序必須使用終端,這將非常有用。 密碼提示執行此操作。
  • 可以從程序tty讀取終端設備(即使重定向stdin等)。
  • 程序可以使用isatty檢查文件描述符 ,看它是否是終端。 如果將流重定向到文件或管道,則它不是終端。

進一步閱讀:

通過實驗,我發現自己有以下答案:

每個三重stderrstdoutstdin都可以通過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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM