2016-03-08 107 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);'会。 –

相关问题