2016-03-08 112 views
1

我一直在尋找int man 3 tcgetattr(因爲我想更改程序中的終端設置)並找到了它。通過tcsetattr(fd .....)設置終端屬性時,fd可以是stdout還是stdin?

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/ptystdinstdoutstderr在一起。

+0

fd表示文件描述符 – HoKy22

+0

@ HoKy22謝謝,'fd'的兩個選項,即'stdin'和'stdout'都是文件描述符,因此它不是很清楚,我需要詢問 – humanityANDpeace

回答

3

fd代表文件描述符,這是一個OS文件對象的引用。因爲它是一個參考,所以多個不同的文件描述符可能指向同一個文件對象。

stdinstdout,並stderrFILE *對象 - 實際指向標準輸入輸出FILE數據結構。您可以使用fileno函數獲取引用基礎OS對象的文件描述符。

所以這裏有兩個層次的間接進行。 FILE *可能都指代相同的FILE,但他們不;有3個單獨的FILE對象stdin,stdoutstderr。這些FILE對象每個都包含一個文件描述符,通常是0,1和2(我通常說 - OS/lib以這種方式設置它們,只有在程序中明確更改它們時它們纔會更改)。這3個文件描述符通常都會引用同一個底層操作系統對象,它是一個終端對象。由於(通常)只有一個終端,並且所有這些文件描述符(通常)都引用它,所以使用哪個fd(0,1或2)作爲tcsetaddr的第一個參數並不重要。

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

+0

關於重定向的好處。 – humanityANDpeace

0

通過實驗的方式,我發現自己下面的回答:

的每個三重stderrstdoutstdin能夠被用來通過tcsetattr(fd....)功能更改終端設置。一旦變化effectuated閱讀tcgsetattr(stdin....)tcgsetattr(stdout....),也tcgsetattr(sterr....)返回在struct termios.h相同的內容可能通過memcmp(&termios_stdin,&termios_stdout,sizeof(struct termios)) == 0

也說不定手冊頁說有點間接這個

tcgetattr(驗證)得到與由 fd引用的對象關聯的參數,並將它們存儲在termios_p引用的termios結構中。 該功能可以從後臺進程調用;然而, 終端屬性可能會隨後由前臺 進程更改。

fd因此通過FD所指的對象始終是相同的終端

1

與達成一致@克里斯-杜德對應於流標準輸入標準輸出stderr的通常指的是相同終端的文件描述符,需要用於原來的問題的一些要點:

  • fd參數(文件描述符)爲tcgetattrtcsetattr必須爲終端
  • 您可以使用fileno獲取此流,例如fileno(stdin)
  • POSIX defines constants文件描述符的默認分配到標準輸入標準輸出標準錯誤STDIN_FILENOSTDOUT_FILENOSTDERR_FILENO。但是,有可能reopen任何流(或使用dupdup2)並更改實際的文件描述符。
  • 雖然您可以獲取流的文件描述符,但是如果您對終端屬性進行任何有趣的操作,可能會干擾用於流的緩衝。如果必須混合兩個文件描述符和流,請在讀取或寫入流之前對終端屬性進行更改。
  • 您還可以在終端設備上使用open獲取文件描述符。如果流被重定向,那麼這很有用,並且您的應用程序必須與終端一起工作。密碼提示執行此操作。
  • 終端設備可以從程序中讀取tty(即使stdin等被重定向)。
  • 程序可以檢查文件描述符使用isatty來查看它是否是終端。如果一個流被重定向到一個文件或管道,它不是一個終端。

進一步閱讀:

1

爲了簡化Thomas Dickey'sChris 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僞終端接口,並且可以很好地工作。 xterm或任何其他普通終端仿真器(或Linux控制檯),在所有POSIXy系統 - Linux,Mac OS X和BSD變體中。

+0

喜歡閱讀您的內容豐富的答案及其代碼,謝謝。 wrstr()和它的write()不會導致系統緩衝的原因是我不明白的。我認爲這完全取決於終端設備的設置,即在'struct termios.c_lflag'中設置'ICANON'標誌應該[使輸入一行一行可用。]](http://man7.org/ LINUX /人-頁/ man3/termios.3.html)。我錯過了什麼,你的好代碼的哪部分確實設置了這個? – humanityANDpeace

+0

@humanityANDpeace:你沒有錯過任何東西,我只是自言自語。我指的是默認情況下標準C I/O爲'stdout'所做的緩衝。本質上,'wrstr()'立即將字符串轉交給內核,就像'fputs(str,stdout); fflush(stdout);'會。 –

相關問題