获取终端宽度在C?


89

我一直在寻找一种从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 isnt 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;
}

1
所提出的答案均不超过一半正确。
Thomas Dickey

2
@ThomasDickey,那你的答案在哪里?
亚历克西斯·威尔克

Answers:


126

您是否考虑过使用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
}

7
是的,但是术语宽度不是环境变量,它对术语而言是静态的。
奥斯汀

4
如果有人在程序执行期间调整终端的大小,则不会提供您当前的终端大小。
克里斯·杰斯特·杨

是的,要补充一点:)
约翰T

如何获得像素大小?我用ws_xpixelws_ypixel,但它只是打印零!
Debashish

@Debashish取决于。例如,Linux根本不支持这些字段。
melpomene

16

这个例子有点冗长,但是我相信这是检测端子尺寸的最便捷的方法。这也处理调整大小事件。

正如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);
}

3
但是从信号处理程序调用initscr和endwin真的安全吗?他们在异步信号安全的API中至少没有列出man 7 signal
导航

1
@nav很好,我从没想过!更好的解决方案也许是让信号处理程序引发一个标志,然后在主循环中执行其余操作吗?
gamen

1
@gamen,是的,那会更好;)-同样使用sigaction而不是signal也会更好。
博多·蒂森

那么COLS和LINES是全局变量吗?
einpoklum '16

1
@AlexisWilke:包括OKERR。它们的“种类”如何帮助我们填补生活中的空白:-(
einpoklum

12
#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手册。


您也可以使用-lcurses进行编译。
坎布斯2011年

2
我知道此评论是在事实发生6年之后提出的,但请解释您的神奇数字2048 ...
einpoklum

1
@einpoklum这已经快三年了,但不是很清楚2048只是缓冲区的任意大小,对于在那里输入的字符串来说“应该足够大”是什么?
Roflcopter4

2
实际上,这个答案有太多的假设是正确的。
托马斯·迪基

1
对于任何好奇的人,此处的GNU termcap文档中都解释了2048缓冲区的大小:gnu.org/software/termutils/manual/termcap-1.3/html_mono / ...还有很多其他内容,阅读这篇文章的人可能会觉得有用。

3

如果已安装ncurses并正在使用它,则可以getmaxyx()用来查找终端的尺寸。


2
是的,做笔记上Y是第一位的,然后X.
丹尼尔

0

假设您使用的是Linux,我想您想使用ncurses库。我很确定您拥有的ttysize内容不在stdlib中。


好吧,我要做的事情真的不值得为您设置ncurses
奥斯丁,2009年

ncurses也不在stdlib中。两者都标准化,POSIX,但ioctl方式更简单和更清洁的,因为你没有初始化的诅咒,等等
Gandaro

0

因此,这里不建议答案,而是:

linux-pc:~/scratch$ echo $LINES

49

linux-pc:~/scratch$ printenv | grep LINES

linux-pc:~/scratch$

好的,我注意到,如果我调整GNOME终端的大小,则LINES和COLUMNS变量会随之变化。

Kinda好像GNOME终端本身正在创建这些环境变量?


1
并确保它不会传递给C代码。getenv(“ LINES”)返回NULL。
Scott Franco

变量是外壳程序,而不是终端程序。
melpomene

0

为了添加更完整的答案,我发现对我有用的是使用@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

size_t* get_screen_size()
{
  size_t* result = malloc(sizeof(size_t) * 2);
  if(!result) err(1, "Memory Error");

  struct winsize ws;
  int fd;

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

  result[0] = ws.ws_row;
  result[1] = ws.ws_col;

  close(fd);

  return result;
}

如果您确定不是全部调用,而是偶尔调用一次,则应该没问题,它甚至应该在用户调整终端窗口大小时更新(因为您每次打开文件并都读取它)。

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

哦,别忘free()result


-1

这是已经建议的环境变量的函数调用:

int lines = atoi(getenv("LINES"));
int columns = atoi(getenv("COLUMNS"));

11
环境变量不可靠。这些值由外壳设置,因此不保证它们存在。另外,如果用户更改终端大小,它们也不会是最新的。
朱利诺(Juliano)

1
许多shell为SIGWINCH信号建立处理程序,因此它们可以使变量保持最新(它们也需要变量,以便在输入编辑器中进行正确的换行)。
Barmar 2014年

5
他们可能会这样做,但是程序的环境在运行时不会被更新。
Functino

当然,该代码很可能崩溃,因为您不测试是否getenv()返回NULL,而是在我的Linux终端中测试(因为未导出这些变量。)而且即使shell更新了这些变量,您也不会看到在程序运行时更改(并非没有自己的SIGWINCH处理程序)。
亚历克西斯·威尔克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.