在C语言中如何获取终端的宽度?

116

我一直在寻找一种方法,在我的C程序内部获取终端宽度。我一直在想的是以下代码:

#include <sys/ioctl.h>
#include <stdio.h>

int main (void)
{
    struct ttysize ts;
    ioctl(0, TIOCGSIZE, &ts);

    printf ("lines %d\n", ts.ts_lines);
    printf ("columns %d\n", ts.ts_cols);
}

但是每次我尝试那样做,都会遇到问题。
austin@:~$ gcc test.c -o test
test.c: In function ‘main’:
test.c:6: error: storage size of ‘ts’ isn’t known
test.c:7: error: ‘TIOCGSIZE’ undeclared (first use in this function)
test.c:7: error: (Each undeclared identifier is reported only once
test.c:7: error: for each function it appears in.)

这是最好的方法吗?还是有更好的方法?如果没有,那么如何让它工作?

编辑:修复后的代码为

#include <sys/ioctl.h>
#include <stdio.h>

int main (void)
{
    struct winsize w;
    ioctl(0, TIOCGWINSZ, &w);

    printf ("lines %d\n", w.ws_row);
    printf ("columns %d\n", w.ws_col);
    return 0;
}
8个回答

159

你是否考虑过使用 getenv()?它可以让你获取系统环境变量,其中包含终端的列数和行数。

或者使用你的方法,如果你想看到内核视为终端大小的大小(最好是在终端被调整大小的情况下),你需要使用TIOCGWINSZ,而不是你的TIOCGSIZE,代码如下:

struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);

完整代码如下:

#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>

int main (int argc, char **argv)
{
    struct winsize w;
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);

    printf ("lines %d\n", w.ws_row);
    printf ("columns %d\n", w.ws_col);
    return 0;  // make sure your main returns int
}

10
是的,但“width”一词不是环境变量,它是固定于该术语的。 - austin
6
如果在程序执行过程中有人调整终端的大小,它就无法为您提供当前的终端尺寸。 - C. K. Young
1
@Debashish 这要看情况。例如,Linux根本不支持那些字段。 - melpomene
2
虽然我们可以在环境中看到LINESCOLUMNS变量,但它们似乎没有被导出。因此无法从您的C程序中访问。 - Alexis Wilke
1
刚刚偶然看到了这个答案,当我意识到在watch(1)下运行时getenv("COLUMNS")完美地工作时,我的下巴都要掉下来了。所以现在我有一组备选方案,全部来自于TIOCWINSZ ioctl,如果不是tty,则使用getenv,最后使用经典的ANSI转义“将光标移动到9999,9999并查询光标位置”。最后一个适用于嵌入式系统的串行控制台 :) - troglobit
显示剩余2条评论

19

这个例子略微冗长,但我认为这是最可移植的检测终端尺寸的方法。它还可以处理调整大小事件。

如tim和rlbond所建议的,我正在使用ncurses。与直接读取环境变量相比,它保证了终端兼容性的显著提高。

#include <ncurses.h>
#include <string.h>
#include <signal.h>

// SIGWINCH is called when the window is resized.
void handle_winch(int sig){
  signal(SIGWINCH, SIG_IGN);

  // Reinitialize the window to update data structures.
  endwin();
  initscr();
  refresh();
  clear();

  char tmp[128];
  sprintf(tmp, "%dx%d", COLS, LINES);

  // Approximate the center
  int x = COLS / 2 - strlen(tmp) / 2;
  int y = LINES / 2 - 1;

  mvaddstr(y, x, tmp);
  refresh();

  signal(SIGWINCH, handle_winch);
}

int main(int argc, char *argv[]){
  initscr();
  // COLS/LINES are now set

  signal(SIGWINCH, handle_winch);

  while(getch() != 27){
    /* Nada */
  }

  endwin();

  return(0);
}

5
在信号处理程序中调用initscr和endwin函数真的安全吗?至少在man 7 signal中列出的异步信号安全API列表中没有它们。 - nav
1
这是个好观点 @nav,我从来没有想过!也许更好的解决方案是让信号处理程序引发一个标志,然后在主循环中执行其余操作? - gamen
1
@gamen,是的,那样会更好;)-而且使用sigaction代替signal也会更好。 - Bodo Thiesen
1
COLS和LINES是全局变量吗? - einpoklum
@einpoklum,是的。ncurses有许多“简单命名”的全局变量供我们使用。https://www.tldp.org/LDP/lpg/node97.html - Alexis Wilke
2
@AlexisWilke:包括 OKERR。他们真是太“友好”了,帮助我们填补生活中的这个空白 :-( - einpoklum

13
#include <stdio.h>
#include <stdlib.h>
#include <termcap.h>
#include <error.h>

static char termbuf[2048];

int main(void)
{
    char *termtype = getenv("TERM");

    if (tgetent(termbuf, termtype) < 0) {
        error(EXIT_FAILURE, 0, "Could not access the termcap data base.\n");
    }

    int lines = tgetnum("li");
    int columns = tgetnum("co");
    printf("lines = %d; columns = %d.\n", lines, columns);
    return 0;
}

需要使用-ltermcap进行编译。使用termcap可以获得许多其他有用的信息。使用info termcap查看termcap手册以获取更多详细信息。


我无法在Ubuntu 14.04中包含termcap,并且在存储库中也找不到它。 :/ - Eric Sebasta
2
我知道这条评论是在事情发生6年后才发出的,但请解释一下你所说的魔数2048的含义... - einpoklum
1
@einpoklum 这已经是将近三年后的事情了,但是难道不很明显吗?2048只是一个缓冲区的任意大小,它“可能足够大”以容纳任何输入字符串。 - Roflcopter4
2
实际上,这个回答做出了太多假设,无法正确。 - Thomas Dickey
2
对于任何好奇的人,2048缓冲区大小在GNU termcap文档中有解释:https://www.gnu.org/software/termutils/manual/termcap-1.3/html_mono/termcap.html#SEC4。此外,还有很多其他内容可能对阅读此帖子的人有用。 - user5739133
显示剩余2条评论

4
为了提供更完整的答案,我发现使用@John_T的解决方案并从Rosetta Code中添加一些内容,再加上一些排除依赖项的故障排除,对我很有效。这可能有点低效,但通过智能编程,您可以使其正常工作,而不必一直打开终端文件。
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h> // ioctl, TIOCGWINSZ
#include <err.h>       // err
#include <fcntl.h>     // open
#include <unistd.h>    // close
#include <termios.h>   // don't remember, but it's needed

struct WinSize {
  size_t rows;
  size_t cols;
};

struct WinSize get_screen_size()
{
  struct winsize ws;
  int fd;

  fd = open("/dev/tty", O_RDWR);
  if(fd < 0 || ioctl(fd, TIOCGWINSZ, &ws) < 0) err(8, "/dev/tty");

  struct WinSize size;
  size.rows = ws.ws_row;
  size.cols = ws.ws_col;

  close(fd);

  return size;
}

如果你确保不要总是调用它,但是偶尔调用一下应该没问题,它甚至会在用户调整终端窗口大小时更新(因为你打开文件并每次都读取它)。

如果您没有使用TIOCGWINSZ,请参阅此表单上的第一个答案 https://www.linuxquestions.org/questions/programming-9/get-width-height-of-a-terminal-window-in-c-810739/

哦,别忘了用free()释放result


4
为什么要返回 size_t *?只需为其创建一个结构体并返回结构体值即可。这样就不需要涉及到 malloc()free() - Mecki
对于使用此代码的人:在调用open时,0_RDWR必须更改为O_RDWR(使用O而不是0)。 - pschulz
@Mecki 当我写这个的时候,我没有意识到 malloc() 的性能成本。 - iggy12345
1
@pschulz,错别字已经被修正。 - iggy12345

3
如果您已经安装了ncurses并正在使用它,您可以使用getmaxyx()函数来查找终端的尺寸。

3
是的,请注意先输入Y,然后再输入X。 - Daniel

0

我的版本是对ioctl方法的消除,我没有分配内存并通过值返回结构体,因此我相信这里不会有内存泄漏

头文件

#include <stdlib.h>
#include <sys/ioctl.h> // ioctl, TIOCGWINSZ

struct winsize get_screen_size();

unsigned short get_screen_width();
unsigned short get_screen_height();

void test_screen_size();

实现方面,我还添加了一个测试函数,它会在终端中填充一个四周都有一个字符填充的框。
/**
* Implementation of nos_utils signatures
*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h> // ioctl, TIOCGWINSZ
#include <err.h>       // err
#include <fcntl.h>     // open
#include <unistd.h>    // close
//#include <termios.h>   // doesnt seem to be needed for this 
#include "nos_utils.h"

/**
 * @return  struct winsize   
 *  unsigned short int ws_row;
 *   unsigned short int ws_col;
 *   unsigned short int ws_xpixel;
 *   unsigned short int ws_ypixel;
 */
struct winsize get_screen_size() {
    struct winsize ws;
    int fd;

    fd = open("/dev/tty", O_RDWR);
    if (fd < 0 || ioctl(fd, TIOCGWINSZ, &ws) < 0) err(8, "/dev/tty");
    close(fd); // dont forget to close files
    return ws;
}

unsigned short get_screen_width() {
    struct winsize ws = get_screen_size();
    return ws.ws_col;
}

unsigned short get_screen_height() {
    struct winsize ws = get_screen_size();
    return ws.ws_row;
}

void test_screen_size() {
    struct winsize ws = get_screen_size();
//    unsigned short  h = ws.ws_row;
//    unsigned short  w = ws.ws_col;
    printf("The Teminal Size is\n rows: %zu  in %upx\n cols: %zu in %upx\n", ws.ws_row, ws.ws_ypixel, ws.ws_col,
           ws.ws_xpixel);
    
    unsigned short  h = get_screen_height();
    unsigned short  w = get_screen_width();
    h = h - 4; //for the 3 lines above + 1 fro new terminal line after :)
    for (unsigned short  i = 0; i < h; i++) {// for each row
        for (unsigned short  j = 0; j < w; j++) { // for each col
            //new line if we are last char
            if (j == w - 1) {
                printf(" \n");
            }//one space buffer around edge
            else if (i == 0 || i == h - 1 || j == 0) {
                printf(" ");
            } //the up arrows
            else if (i == 1) {
                printf("^");
            } else if (i == h - 2) {
                printf("v");
            } else if (j == 1) {
                printf("<");
            } else if (j == w - 2) {
                printf(">");
            } else {
                printf("#");
            }
        }//end col
    }//end row
}

int main(int argc, char** argv) {
    test_screen_size();
    return 0;
}

running test


-1

假设您正在使用Linux,我认为您想要使用ncurses库。我非常确定您拥有的ttysize内容不在stdlib中。


嗯,我所做的事情并不值得为之设置ncurses。 - austin
2
ncurses也不在stdlib中。它们都在POSIX中标准化,但是ioctl的方式更简单、更清晰,因为你不需要初始化curses等。 - Gandaro

-3
以下是已建议的环境变量的函数调用:
int lines = atoi(getenv("LINES"));
int columns = atoi(getenv("COLUMNS"));

13
环境变量不可靠。这些值由shell设置,因此不能保证它们存在。而且如果用户更改终端大小,它们将不会是最新的。 - Juliano
1
许多shell都为SIGWINCH信号建立了处理程序,以便它们可以保持变量的最新状态(它们还需要这样做才能在输入编辑器中进行适当的换行)。 - Barmar
5
他们很可能会这样做,但程序的环境不会在运行时更新。 - Functino
1
当然,那段代码很可能会崩溃,因为你没有测试getenv()是否返回NULL,在我的Linux终端中它确实返回了NULL(因为这些变量没有被导出)。即使shell更新了这些变量,你也看不到程序运行时的变化(除非你有自己的SIGWINCH处理程序)。 - Alexis Wilke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接