脚本中丢失的LINES和COLUMNS环境变量


72

考虑以下:

me@mine:~$ cat a.sh 
#!/bin/bash
echo "Lines: " $LINES
echo "Columns: " $COLUMNS
me@mine:~$ ./a.sh 
Lines: 
Columns: 
me@mine:~$ echo "Lines: " $LINES
Lines:  52
me@mine:~$ echo "Columns: " $COLUMNS
Columns:  157
me@mine:~$ 

变量$LINES$COLUMNS是外壳变量,而不是环境变量,因此不会导出到子进程中(但当我调整xterm窗口的大小时,即使从远程位置通过ssh登录,它们也会自动更新)。有没有一种方法可以让我的脚本知道当前的终端大小?

编辑:我需要这样做作为解决此问题的方法:每次使用vi(以及vim,less和类似的命令)都会弄乱屏幕。更改终端不是一种选择,因此,我正在寻找解决方法(确定向下滚动$LINES肯定不是完美的解决方案,但至少比丢失前一个屏幕更好)


我猜您可以通过对vi的“ Ctrl-L”命令解决您的原始问题。
09年

@ndim:谢谢您的建议,但是您应该在另一个问题上写它(我会回答您这不起作用)
Davide

Davide一时兴起,我一直滚动到最下方,找到了Cy的答案,我很高兴自己做到了。您可能需要考虑切换您接受的答案。这将帮助很多发现此问题的人。
mr_carrera

Answers:


85

您可以从获得行和列tput

#!/bin/bash

lines=$(tput lines)
columns=$(tput cols)

echo "Lines: " $lines
echo "Columns: " $columns

5
对于cygwin 1.7或更高版本,您需要安装ncurses软件包以获取tput。
AleksanderBlomskøld2012年

2
这应该通过以下方式加以保护:if [-n“ $ {TERM}”]; 然后lines = $(tput lines); 其他...
MarcH 2015年

2
@Aleksander该stty命令包含在Cygwin的coreutils中,tput如果我们不需要安装ncurses ,可以使用该命令。我用示例添加了答案。
Cy Rossignol

在Alpine上,您需要ncurses
webjay

1
我的航站楼有197列,但tput总是给我80分
daddinhquoc

37

因为这个问题很受欢迎,所以我想添加一个新的答案以及一些其他信息。

通常,在现代系统上,$COLUMNS$LINES变量不是环境变量。Shell在每个命令之后动态设置这些值,我们通常无法从非交互式脚本访问它们。如果我们导出这些值,有些程序会尊重这些值,但是这种行为并未得到标准化或普遍支持。

当我们使用以下方法启用该选项时,Bash会在流程范围(而非环境)中设置这些变量checkwinsize

shopt -s checkwinsize 

许多系统在默认或系统范围的启动文件(/ etc / bashrc或类似文件)中为我们启用此选项,因此我们需要记住,这些变量可能并不总是可用。在某些系统上,例如Cygwin,我们启用此选项,因此不会设置Bash $COLUMNS$LINES除非我们执行上面的行或将其添加到〜/ .bashrc中

便携式方法

当写非交互式脚本,我们通常不希望依赖于$LINES$COLUMNS默认情况下(但我们可以检查这些允许如果需要,用户可以手动覆盖终端大小)。

而是,sttytput实用程序提供了一种可移植的方法,可以根据脚本确定终端大小(以下描述的命令当前正在针对POSIX进行标准化)。

Puppe接受的答案所示,我们可以用tput一种非常简单的方式来收集终端大小:

lines=$(tput lines)
columns=$(tput cols)

或者,size查询可以stty一步一步给我们提供终端行和列的数量(输出为行数,后跟两个空格,后跟列数):

size=$(stty size)  # "40  80" for example 

stty程序通常与GNU Coreutils一起提供,因此我们经常可以在没有的系统上找到它tput。有时我更喜欢这种stty方法,因为我们调用的命令和子外壳要少一些(在Cygwin上很昂贵),但是它确实要求我们将输出解析为行和列,因此可读性较差:

lines=${size% *}
columns=${size#* }

上述两种方法都可以在任何POSIX Shell中使用。

非便携式方法

如果我们不在乎可移植性,那么Bash支持流程替换以简化前面的示例:

read lines columns < <(stty size) 

...比tput示例运行得更快,但stty至少在我的机器上仍比第一个实现慢。实际上,对性能的影响可以忽略不计-选择最适合该程序的方法(或基于目标系统上可用的命令)。

对于Bash 4.3和更高版本,我们可以利用该checkwinsize选项来避免依赖于另一个程序。当我们在脚本中启用此选项时,在子进程退出,Bash会进行设置$LINES$COLUMNS就像在交互式提示中一样:

#!/bin/bash
shopt -s checkwinsize
cat /dev/null # Refresh LINES and COLUMNS

...就像子外壳退出时一样:

shopt -s checkwinsize
(: Refresh LINES and COLUMNS)

如果启用此选项,Bash将在每次外部命令调用后获取终端大小,因此我们可能希望在初始化变量后将其关闭:

shopt -u checkwinsize

如果由于某种原因,我们仍想使用$LINES,并$COLUMNS在我们的脚本环境,我们可以配置猛砸这些变量导出到环境:

trap 'export LINES COLUMNS' DEBUG

BashDEBUG陷阱在提示符下输入每个命令之前执行,因此我们可以使用它来导出这些变量。通过使用每个命令重新导出它们,我们可以确保在终端大小更改时环境变量保持最新状态。将此行与上面显示的选项一起添加到.bashrccheckwinsize中。它适用于个人脚本,但是我不建议在将要共享的任何脚本中使用这些变量。


1
很棒的答案。最终了解了为什么系统的行为如此不同...登录仅是为了支持它-并提示一个小错误:匹配size输出时,您还原的行和列应为.eg lines=${size#* }size输出第一行,然后输出列。
Red Pill

@RedPill我是个傻瓜:)在实际的计算机上而不是在手机上检查了此内容,而我只是在误读这些值。实际上,POSIX提议将格式定义为"%1dΔ%1d\n", <rows>, <columns>,因此我们可以依赖于stty首先输出行,然后输出列的假设。我将更新答案...谢谢!
Cy Rossignol,

8
eval $( resize )

做那个工作...(在基于xterm的终端上)



6

为了完整起见,让我提及设置“ checkwinsize”选项正是OP所需要的,但是有一个陷阱。在非交互式脚本中,默认情况下未设置该属性,但是您可以选择在任何脚本的开头添加以下行来启用它:

shopt -s checkwinsize

不幸的是,设置选项后,LINES和COLUMNS变量没有立即设置(至少是我上次尝试的时间)。相反,您需要强制Bash等待子shell完成,此时它将设置这些变量。因此,针对此问题的仅Bash完整解决方案是使用以下行启动脚本:

shopt -s checkwinsize; (:;:)

然后,您可以将LINES和COLUMNS变量用于您的心脏,每次调整终端大小时,它们将被重置为正确的值,而无需调用任何外部实用程序。


这似乎仅适用于bash的新版本。它不适用于较旧的bash版本(在centos7上为4.2.46,在debian7上为4.2.37)。但是,tput方法(请参阅Puppe的答案)似乎无处不在。
扬·拉林斯基

4

您是否尝试过让您的shebang说:

#!/bin/bash -i

另外,请参阅我对所引用问题的回答。将t_ti变量设置为null可能会有所帮助vimstackoverflow.com/questions/630519/…–
丹尼斯·威廉姆森

不幸的是,无论在AIX中还是在Linux中#!/bin/bash -i都没有任何区别
Davide

1
我从您的脚本中得到空白输出,其中没有-i和正确的数字。这是在Ubuntu上的(不导出LINES和COLUMNS)。我发现在Cygwin上,我必须导出两个变量(您可以在〜/ .bashrc中执行此操作),以使其正常工作,并且不需要此变量-i。但是,kill -SIGWINCH $$如果我调整窗口大小(对于Cygwin),则必须在Bash提示符下进行操作以获取更新的值。
丹尼斯·威廉姆森

Ubuntu的是什么?在Hardy Haron上,-i没有任何区别:带有或不带有空白输出(无论如何,我需要在AIX而不是Ubuntu上使用)
Davide

1
不好的主意,因为对于bash而言,-i选项意味着外壳是交互式的。因此,这具有运行〜/ .bashrc文件的副作用。
MarkHu 2012年

3

跑步help export也许有帮助?

me@mine:~$ cat a.sh 
#!/bin/bash
echo "Lines: " $LINES
echo "Columns: " $COLUMNS
me@mine:~$ ./a.sh 
Lines: 
Columns: 
me@mine:~$ echo "Lines: " $LINES
Lines:  52
me@mine:~$ echo "Columns: " $COLUMNS
Columns:  157
me@mine:~$ export LINES COLUMNS
me@mine:~$ ./a.sh 
Lines:  52
Columns:  157
me@mine:~$ 

1
不知道为什么这被否决。我export LINES=$LINESexport COLUMNS=$COLUMNS我的.bashrc,并正常工作对我来说。你不具备与tput的混乱。
tandrewnichols

2

$LINES$COLUMNS在bash中,它只是TTY ioctl周围的shell-y包装器,可为您提供TTY的大小以及每次大小更改时终端发送的信号。

您可以使用其他某种语言编写程序,该程序直接调用这些ioctl以获取TTY尺寸,然后使用该程序。

编辑:好吧,原来该程序已经存在,并称为tput。投票给Puppetput基于答案的答案


2
#!/bin/bash -i

-i现在可以在Ubuntu 11.10上与bash 4.2.10(1)-release一起使用。

$ cat show_dimensions.sh 
#!/bin/bash -i
printf "COLUMNS = %d\n" $COLUMNS
printf "LINES = %d\n" $LINES

$ ./show_dimensions.sh 
COLUMNS = 150
LINES = 101

$ bash --version
GNU bash, version 4.2.10(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

数字的确会随着窗口大小的变化而变化;陷阱显示脚本正在获取SIGWINCH。


在Cygwin(mintty)和4.1.10(4)版本下,LINES / COLUMNS仍然为空,但可以使用tput。
Andreas Spindler

1

为什么不在exec命令上使用环境变量,如下所示:

docker exec -ti -e LINES=$LINES -e COLUMNS=$COLUMNS  container /bin/bash

-1

我的经验是,您应该在'之前启动脚本。script_to_run”形式,而不是“ scritp_to_run”形式。简单检查如下:

'(( ${#COLUMNS} )) || { echo "Try start like '. name'" ; return 1 ; }

2
这种执行方式称为源,而.命令称为source。并非所有脚本都编写为支持这种执行方式。例如,当脚本调用时exit,不仅是脚本,还会退出提供脚本的整个外壳。请参阅之间有什么区别?shsource
Palec
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.