重定向 stderr 会更改 tput 的输出,但仅在将输出捕获到变量时才会更改?
Redirecting stderr changes the output of tput but only when capturing the output to a variable?
我正在尝试在脚本中获取当前终端 window 的宽度,并使用 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
好吧,我迷路了!有人可以向我解释一下这是怎么回事吗?
有部分答案here
tput cols
可以根据这些情况导致来自不同来源的数据:
- fd 1,2 之一是 tty
- fd 1,2 都不是 tty
当 运行 tput cols
: 终端设置 columns
可能被获取
如果可能,使用 fd 1,2 的 ioctl()
,否则获取 terminfo
能力 cols
.
本期设置终端列99,
$ stty columns 99; stty -a | grep columns
speed 38400 baud; rows 24; columns 99; line = 0;
只有 fd 1 被重定向(到 non-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 时获取的能力,
创建并安装了名为 tmp
且具有不同 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退出non-zero:
$ echo $(tput kol 2> /dev/null || echo 100)
100
实际上 Milag 的答案解决了这个谜题! 好吧,它有点复杂,真正的答案有点隐藏在所有给出的细节中,所以我提供了一个对于感兴趣的 reader,这里的回复更容易理解,但是荣誉归于 Milag,因此我会接受这个答案(也是为了声誉)。
简而言之,事情是这样的:
tput cols
需要 tty
才能获得真正的终端宽度。当我打开终端 window 时,stdout
和 stderr
都是 tty
,因此
tput cols
打印出正确的结果。如果我现在将 stderr
重定向到 /dev/null
,如
tput cols 2>/dev/null
那么stderr
不再是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
参数,根本不产生任何可见输出或捕获任何内容,我只想知道退出代码是什么。如果它确实支持这个论点,我捕获输出,否则我直接使用回退。
我正在尝试在脚本中获取当前终端 window 的宽度,并使用 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
好吧,我迷路了!有人可以向我解释一下这是怎么回事吗?
有部分答案here
tput cols
可以根据这些情况导致来自不同来源的数据:
- fd 1,2 之一是 tty
- fd 1,2 都不是 tty
当 运行 tput cols
: 终端设置 columns
可能被获取
如果可能,使用 fd 1,2 的 ioctl()
,否则获取 terminfo
能力 cols
.
本期设置终端列99,
$ stty columns 99; stty -a | grep columns
speed 38400 baud; rows 24; columns 99; line = 0;
只有 fd 1 被重定向(到 non-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 时获取的能力,
创建并安装了名为 tmp
且具有不同 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退出non-zero:
$ echo $(tput kol 2> /dev/null || echo 100)
100
实际上 Milag 的答案解决了这个谜题! 好吧,它有点复杂,真正的答案有点隐藏在所有给出的细节中,所以我提供了一个对于感兴趣的 reader,这里的回复更容易理解,但是荣誉归于 Milag,因此我会接受这个答案(也是为了声誉)。
简而言之,事情是这样的:
tput cols
需要 tty
才能获得真正的终端宽度。当我打开终端 window 时,stdout
和 stderr
都是 tty
,因此
tput cols
打印出正确的结果。如果我现在将 stderr
重定向到 /dev/null
,如
tput cols 2>/dev/null
那么stderr
不再是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
参数,根本不产生任何可见输出或捕获任何内容,我只想知道退出代码是什么。如果它确实支持这个论点,我捕获输出,否则我直接使用回退。