循环回显以显示'实时更新'多个变量

时间:2017-01-24 01:47:17

标签: linux bash echo sh

我需要一个能够在多个线路上显示终端中几个实时数据流输出的功能。理想的结果将打印到终端,如下所示:

Measurement 1: #### 
Measurement 2: ####

####以1s为间隔更新,但文本的其余部分保持原样在屏幕上。

此问题已针对单行输出here解决,但我无法弄清楚如何扩展到多行。

这是我到目前为止所拥有的:

function doit {
  while :;
  do
    i=$(current reading from sensor 1)
    j=$(current reading from sensor 2)
    echo -ne  "Measurement 1: $i\nMeasurement 2: $j"'\r';
    sleep 1;
  done
}

这导致连续输出仅在终端上的第二行上写入,在循环的每次迭代之后在终端上创建新的输出块。

提出的另一个解决方案here是:

function doit {
  while :;
  do
    i=$(current reading from sensor 1)
    j=$(current reading from sensor 2)
    echo -ne  "Measurement 1: $i\nMeasurement 2: $j"'\r';
    sleep 1;
    tput cuu1
    tput el
    tput cuu1
    tput el
  done
}

这很接近,但在每次循环后清除终端上显示的所有文本,这会在屏幕上和屏幕外闪烁文本块时产生令人不快的视觉效果。这将必须工作,以便有人可以密切监视读数。我需要将其扩展到5行或更多行。感谢您的任何建议,并期待看到一些解决方案!

4 个答案:

答案 0 :(得分:1)

这是一个试图通过在每行末尾发送删除序列来避免闪烁屏幕的版本(通常不会删除任何内容,但如果前一行更长则会有帮助)。我使用$RANDOM来表示“获取测量”操作,并且由于这是一个数字,我使用%5d格式,以便在每个周期中数字对齐相似(这也有助于避免显示伪影)。

function doit {
  local up2=$(tput cuu 2)$(tput cr) up=
  local endl=$(tput el)$'\n'
  while :; do
    local i=$RANDOM
    local j=$RANDOM
    printf "%sMeasurement 1: %5d%sMeasurement 2: %5d%s" "$up" "$i" "$endl" "$j" "$endl"
    up=$up2
    sleep 1;
  done
}

答案 1 :(得分:0)

除非屏幕上还有其他需要显示的内容,否则您可以在每个周期清除屏幕。在这种情况下,您不需要-n和echo语句。您也不需要tput命令。

#!/bin/bash
function doit {
  while :;
  do
  i=$(current reading from sensor 1)
  j=$(current reading from sensor 2)
  clear
  echo -e  "Measurement 1: $i\nMeasurement 2: $j"'\r';
  sleep 1;
  done
}

答案 2 :(得分:0)

如果我们太靠近屏幕底部并且输出导致滚动,那么仅使用tput sctput rc存储和恢复光标位置的直观方法就会中断。

我们只需将光标向上移动一次,而不是trying to figure out what line we're currently on,我们可以按照tput cuu n打印的行数再次移动光标。在此之后,我们使用tput ed擦除到屏幕的末尾。

如果读取传感器需要一段时间,这会闪烁(用sleep 0.1模拟),所以我们在打印新线之前擦除线,但是在读出传感器之后。因为这不应该在第一次发生时,我们在第一次打印后设置标记notfirst以指示应该调用clearlines

总而言之:

#!/bin/bash

clearlines () {
    local nlines=$1
    tput cuu $nlines
    tput ed
}

display () {
    while true; do
        # Get "sensor measurements"
        sleep 0.1   # Simulate lag
        local vars=($RANDOM $RANDOM $RANDOM $RANDOM $RANDOM)

        # If this isn't the first output, clear the previous one
        if [[ -n $notfirst ]]; then
            clearlines ${#vars[@]}
        fi

        # Loop over "measurements" to print
        local var
        local i=0
        for var in "${vars[@]}"; do
            printf 'Measurement %d: %d\n' $(( ++i )) "${var}"
        done
        sleep 1

        # Set flag
        local notfirst=1
    done
}

display

答案 3 :(得分:0)

已经有人指出,任何收集传感器数据的延迟都可能是一个闪烁因素,或者可能导致其他显示奇怪。

所以这是另一种对待,以最小的形式。

#!/usr/bin/env/ bash

# This generates random sensor data and stores it in predictably named temp files,
# after a short random delay.
function gather() {
    while sleep $(( $RANDOM % 3 )); do
        printf '%2d\n' $(( $RANDOM % 100 )) > /tmp/t.$$.$1
    done
}

main() {

    # Background our data gatherers...
    gather i &
    gather j &

    # Print a couple of blank lines, since our format starts with "up"
    printf '\n\n'

    # and loop.
    while sleep 1; do
        # gather our randomly generated sensor data...
        i=$( < /tmp/t.$$.i )
        j=$( < /tmp/t.$$.j )
        # and print it.
        printf "$fmt" "$i" "$j"
    done
}

cleol=$(tput el)
up2=$(tput cuu1; tput cuu1)
fmt="${up2}Measurement 1: %d${cleol}\nMeasurement 2: %d${cleol}\n"

main

光标移动很有趣。 : - )

但所有这一切的要点是负责编写输出的循环非常轻量级。它从静态文本文件中获取数据,并打印出来。然后重复。所有繁重的工作都在后台gather()功能中完成,该功能以可能与您的显示更新频率不同的频率更新文本文件。

请注意,正如所写的那样,存在一种竞争条件,即“发送”时,“读取”可能会触发文件,但在写入数据之前。为了尽量减少这种情况,你可以通过以下方式解决竞争:

function gather() {
    while sleep 1; do
        value=$(
          # This simulates the delay of a slow sensor read.
          sleep $(( $RANDOM % 3 ))
          printf '%2d\n' $(( $RANDOM % 100 ))
        )
        printf '%d\n' "$value" > /tmp/t.$$.$1
    done
}

这样更好,并且可能“足够好”,但是写入这样的临时文件不是原子文件系统操作,因此竞争条件的可能性仍然存在。

在下面的变体中,我们模拟读取数据时的长时间延迟,但读取命令的输出转到另一个临时文件,在完成传感器读取后会被符号链接。

function gather() {
    local n=0 item="${1//[^[:alnum:]]/}"
    while :; do
        old="$(readlink /tmp/t.$$.$item)"
        (
          # This simulates the delay of a slow sensor read.
          sleep $(( $RANDOM % 3 ))
          printf '%d\n' $(( $RANDOM % 100 ))
        ) > /tmp/t.$$.$item.$n
        test -s /tmp/t.$$.$item.$n &&
          ln -sfn /tmp/t.$$.$item.$n /tmp/t.$$.$item &&
        test -f "$old" &&
          rm -f "$old"
        ((n++))
        sleep 1
    done
}

括号中的所有内容都是您为生成传感器输出而运行的内容。如果您害怕耗尽$n的值(但不怕质子衰减),您可以切换到$RANDOM而不是使用递增计数器。

您是否考虑过将现有的监控系统用于监控的任何内容?在我看来,像这样的问题已经解决了。 : - )

<强> UPDATE ...

仅仅因为我没有充分利用我的时间,这里有一个改进的main()函数和脚本的其余部分:

function main() {

    # Background our data gatherers...
    local -a pids=()
    for thing in "${things[@]}"; do
        gather $thing &
        pids+=($!); echo "Backgrounded $thing at $!"
    done
    trap "echo 'Quitting...'; kill $(printf '%s ' "${pids[@]}"); exit 0" 2
    trap "echo 'Aborting...'; kill $(printf '%s ' "${pids[@]}"); exit 1" 1 3 15

    # Wait for gatherers to have at least one round of data
    sleep 2

    # and loop.
    local -A v=()
    while sleep 1; do
        # gather our randomly generated sensor data...
        for thing in "${things[@]}"; do
            v[$thing]=$( < /tmp/t.$$.$thing )
        done
        # and print it.
        printf "$fmt" "${v[@]}"
    done
}

things=( i j )                # the list of things to monitor
cleol=$(tput el)              # you know what this is already
up2=$(tput cuu1; tput cuu1)   # this too
fmt="${up2}Measurement 1: %d${cleol}\nMeasurement 2: %d${cleol}\n"

main

这会根据需要将受监视项目列表放在bash数组$things[]和背景中。它会错误地捕获错误并中断。它没有做的一件事是使你的$fmt适应数组的大小。或者煮咖啡。