【问题标题】:LINES and COLUMNS environmental variables lost in a scriptLINES 和 COLUMNS 环境变量在脚本中丢失
【发布时间】:2010-12-19 07:47:51
【问题描述】:

考虑以下几点:

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 是shell 变量,不是 环境变量,因此不会导出到子进程(但是当我调整xterm 窗口大小时它们会自动更新,甚至从远程位置通过 ssh 登录时)。有没有办法让我的脚本知道当前的终端大小?

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

【问题讨论】:

  • 我猜你可以用“Ctrl-L”命令来解决你原来的问题。
  • @ndim:谢谢你的建议,但你应该把它写在另一个问题上(我会回答你说它不起作用)
  • Davide,一时兴起,我一直滚动到底部并找到了 Cy 的答案,我真的很高兴我做到了。您可能需要考虑切换您接受的答案。它将帮助很多找到这个问题的人。

标签: bash shell terminal environment-variables


【解决方案1】:

你可以从tput获取行和列:

#!/bin/bash

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

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

【讨论】:

  • 对于 cygwin 1.7 或更高版本,您需要安装 ncurses 包才能获取 tput。
  • 这应该被保护: if [ -n "${TERM}" ];然后行=$(输入行);否则...
  • @Aleksander stty 命令包含在 Cygwin 上的 coreutils 中,如果我们不需要安装 ncurses,我们可以使用它来代替 tput。我用一个例子添加了一个答案。
  • 在 Alpine 上你需要ncurses
  • 我的终端有 197 个 cols,但 tput 总是给我 80 个
【解决方案2】:

因为这个问题很受欢迎,所以我想添加一个更新的答案,并提供一些额外的信息。

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

当我们启用checkwinsize 选项时,Bash 在进程(而不是环境)范围内设置这些变量:

shopt -s checkwinsize 

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

便携式方法

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

相反,sttytput 实用程序提供了可移植 方法来通过脚本确定终端大小(下面描述的命令是 currently undergoing standardization for POSIX)。

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

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

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

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

stty 程序通常附带GNU Coreutils,因此我们经常可以在没有tput 的系统上找到它。我有时更喜欢stty 方法,因为我们调用的命令和子shell(在Cygwin 上很昂贵),但它确实需要我们将输出解析为行和列,这可能不太可读:

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

上述两种方法都适用于任何 POSIX shell。

非便携式方法

如果我们不关心可移植性,Bash 支持process substitution 以简化前面的示例:

read lines columns < <(stty size) 

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

对于 Bash versions 4.3 and later,我们可以利用 checkwinsize 选项来避免对另一个程序的依赖。当我们在脚本中启用此选项时,Bash 将设置 $LINES$COLUMNS,就像它对交互式提示所做的那样子进程退出:

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

...就像子shell退出时一样:

shopt -s checkwinsize
(: Refresh LINES and COLUMNS)

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

shopt -u checkwinsize

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

trap 'export LINES COLUMNS' DEBUG

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

【讨论】:

  • 很棒的答案。终于明白为什么系统的行为如此不同了......登录只是为了对此表示赞成 - 并提示一个小错误:您在匹配 size 输出时恢复了行和列,应该是 .e.g. lines=${size#* }size 输出第一行然后列。
  • @RedPill 我是个傻瓜 :) 在一台实际的计算机而不是我的手机上检查了这一点,我只是误读了这些值。事实上,POSIX 提案将格式定义为 "%1dΔ%1d\n", &lt;rows&gt;, &lt;columns&gt;,因此我们可以依赖 stty 先输出行,再输出列的假设。我会更新答案...谢谢!
  • GNU Bash 5.0 的手册页说checkwinsize 选项默认启用。
【解决方案3】:
eval $( resize )

做这项工作吗...(在基于 xterm 的终端上)

【讨论】:

    【解决方案4】:
    kill -s WINCH $$
    

    确实设置了变量。

    【讨论】:

    • 我不认为这就是他要寻找的东西
    • 不过很有趣。
    【解决方案5】:

    为了完整起见,让我提一下,设置 'checkwinsize' 选项正是 OP 正在寻找的,但有一个问题。它在非交互式脚本中默认未设置,但您可以选择在任何脚本的开头添加以下行以启用它:

    shopt -s checkwinsize
    

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

    shopt -s checkwinsize; (:;:)
    

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

    【讨论】:

    • 这似乎只适用于新版本的 bash。它不适用于旧版本的 bash(centos7 上的 4.2.46 和 debian7 上的 4.2.37)。然而, tput 方法(见 Puppe 的回答)似乎无处不在。
    • @JánLalinský 不,它有/没有,见here
    【解决方案6】:

    你试过让你的shebang说:

    #!/bin/bash -i
    

    【讨论】:

    • 另外,请参阅我对您引用的问题的回答。将t_ti 变量设置为null 可能有助于vimstackoverflow.com/questions/630519/…
    • 不幸的是,#!/bin/bash -i 在 AIX 和 linux 中都没有任何区别
    • 我从你的脚本中得到空白输出,没有-i 和正确的数字。这是在 Ubuntu 上(不导出 LINES 和 COLUMNS)。我发现在 Cygwin 上,我必须导出两个变量(您可以在 ~/.bashrc 中执行此操作)才能使其正常工作,并且不需要 -i。但是,如果我调整窗口大小(对于 Cygwin),我必须在 Bash 提示符下执行 kill -SIGWINCH $$ 以获取要更新的值。
    • Ubuntu 什么?在 Hardy Haron 上,-i 没有任何区别:有或没有它的空白输出(无论如何,我在 AIX 上需要这个,而不是 Ubuntu)
    • 坏主意,因为对于 bash,-i 选项意味着 shell 是交互式的。所以这会产生副作用,比如运行你的 ~/.bashrc 文件。
    【解决方案7】:

    运行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:~$ 
    

    【讨论】:

    • 不知道为什么这被否决了。我的 bashrc 中有 export LINES=$LINESexport COLUMNS=$COLUMNS,这对我来说是正确的。你没有来搞乱tput。
    【解决方案8】:

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

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

    编辑:嗯,原来该程序已经存在,并且被称为tput。投票给Puppe's tput based answer

    【讨论】:

      【解决方案9】:
      #!/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)-release LINES/COLUMNS 下仍然是空的,但 tput 可以工作。
      【解决方案10】:

      为什么不在这样的 exec 命令中使用环境变量:

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

      【讨论】:

        【解决方案11】:

        我的经验是您应该以 '. script_to_run' 形式,而不是 'scritp_to_run'。简单检查如下:

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

        【讨论】:

        • 这种执行方式称为采购,. 命令称为source。并非所有脚本都支持这种执行方式。例如。当脚本调用exit 时,提供脚本的整个shell 都会退出,而不仅仅是脚本。见What is the difference between sh and source?
        猜你喜欢
        • 1970-01-01
        • 2022-01-16
        • 1970-01-01
        • 1970-01-01
        • 2016-03-06
        • 1970-01-01
        • 2012-03-12
        • 2013-09-14
        • 2012-01-12
        相关资源
        最近更新 更多