1

我正在尝试在脚本中获取当前终端窗口的宽度,并使用 80 作为后备以防万一。我认为这很简单:

cols=$( tput cols || echo 80 ) ; echo $cols
# -> 100

100 是正确的,我将终端设置为 100 个字符宽。由于cols没有符合 POSIX 的参数tput,因此并非所有系统都支持它,因此是回退。现在让我们测试回退:

cols=$( tput colsx || echo 80 ) ; echo $cols
# -> tput: unknown terminfo capability 'colsx'
# -> 80

嗯……不太好。我不想看到那个错误。它印在 上stderr,所以让我们压制它:

cols=$( tput colsx 2>/dev/null || echo 80 ) ; echo $cols
# -> 80

是的,这样好多了。所以最终的代码是

cols=$( tput cols 2>/dev/null || echo 80 ) ; echo $cols 
# -> 80

80?怎么回事?为什么它现在遇到后备?让我们试试这个:

cols=$( tput cols 2>/dev/null ) ; echo $cols  
# -> 80

啊……重定向stderr改变了输出tput?这怎么可能?让我们确认一下:

tput cols 2>/dev/null
# -> 100

好吧,现在我迷路了!有人可以解释一下这里发生了什么吗?

4

2 回答 2

2

这里有部分答案

tput cols可以根据这些情况导致来自不同来源的数据:

  • fd 1,2 之一是 tty
  • fd 1,2 都不是 tty

运行时:如果可能,可以使用 using fd 1,2获取tput cols终端设置,否则获取 terminfo 功能。columnsioctl()cols

本期为终端栏目设置99,

$ stty columns 99; stty -a | grep columns
        speed 38400 baud; rows 24; columns 99; line = 0;

只有 fd 1 重定向(到非 tty):

$ tput cols > out; cat out
99

只有 fd 2 重定向:

$ tput cols 2> err; wc -c err
99
0 err

fd 1,2 均重定向:

$ tput cols > out 2>&1; cat out
80

fd 1 不是 tty:

$ echo $(tput cols || echo 100)
99

fd 1,2 不是 tty:

$ echo $(tput cols 2> /dev/null || echo 100)
80

为了显示cols重定向 fd 1,2 时获取的 cabability,创建并安装了一个名为tmpdifferent的 terminfo,然后:cols

$ export TERM=tmp TERMINFO=$HOME/.ti
$ infocmp | grep cols
        colors#8, cols#132, it#8, lines#24, pairs#64,

fd 1,2 不是 tty:

$ echo $(tput cols 2> /dev/null || echo 100)
132

假上限,tput 退出非零:

$ echo $(tput kol 2> /dev/null || echo 100)
100
于 2020-10-03T17:01:30.203 回答
1

其实米拉格的答案解开了谜团!嗯,它有点复杂,真正的答案有点隐藏在所有细节中,所以我在这里为感兴趣的读者提供一个更容易理解的回复,但荣誉归于 Milag,因此我会接受这个答案(也为了名誉)。

简而言之,这是发生了什么:

tput cols需要 atty来获得真正的终端宽度。当我打开一个终端窗口时,两者stdout都是stderrs tty,因此

tput cols

打印正确的结果。如果我现在重定向stderr/dev/null,如

tput cols 2>/dev/null

thenstderr不再是 a tty,而是一个 char 文件。然而,这没有问题,stdout仍然是一个tty.

但是,如果我在变量中捕获输出,如

cols=$( tput cols 2>/dev/null )

stdout不再tty是任何一个,它是一个通往 shell 的管道,它捕获命令的输出。现在tput根本没有tty,因此无法再获得实际宽度,所以它使用了一些后备,这个后备在我的系统上报告了 80(其他系统可能有更好的后备机制并且仍然报告正确的值)。

所以对于我的脚本,我将不得不解决这个问题:

if tput cols >/dev/null 2>&1; then
    cols=$( tput cols )
else
    cols=80
fi

第一个 if 检查是否tput知道cols参数,根本不产生任何可见的输出或捕获任何东西,我只想知道退出代码是什么。如果它确实支持这个参数,我捕获输出,否则我直接使用回退。

于 2020-10-05T18:12:07.520 回答