10

On a Linux machine I have a series of commands that offer numerical values of the state of different sensors.

The call of these commands is something similar to the following:

$ command1
5647
$ command2
76
$ command3
8754

These values change in real time, and every time I want to check the status of one of them, I have to re-launch the command... This doesn't do me any good since I need both hands to manipulate the hardware.

My goal is to make a simple Bash Script that calls these commands and keeps the value updated (in real time asynchronously or refreshing the value every x seconds) like this:

$ ./myScript.sh
command1: x
command2: y
command3: z
command4: v

Where x, y, z and v are the changing values.

Bash allows this simply and efficiently? or should I choose to do it in another language, like Python?

UPDATE with more info:

My current script is:

#!/bin/bash
echo "Célula calibrada: " $(npe ?AI1)
echo "Anemómetro: " $(npe ?AI2)
echo "Célula temperatura: " $(npe ?AI3)
echo "Célula temperatura: " $(npe ?AI4)

npe being an example command that returns the numeric value. I expect an output like this:

enter image description here

This output I get with the command watch -n x ./myScript.sh, where x is refresh value of seconds. If I edit my script like this:

#!/bin/bash
while sleep 1; do
   clear; # added to keep the information in the same line 
   echo "Célula calibrada: " $(npe ?AI1);
   echo "Anemómetro: " $(npe ?AI2);
   echo "Célula temperatura: " $(npe ?AI3);
   echo "Célula temperatura: " $(npe ?AI4);
done

I get my output with an annoying flicker:

enter image description here

2
  • the flicker comes from ` clear` Nov 16, 2019 at 13:53
  • I know, thanks, the incidence comes by the way of configuring the output of the script as the output of watch, keeping lines but moving values Nov 18, 2019 at 15:48

10 Answers 10

16

You can use tput cup 0 0 to send the cursor up to the top left of the screen. clear once.

#!/bin/bash
clear
while sleep 1; do
    tput cup 0 0
    printf "%21s %6d    \n" \
      "Célula calibrada: "   $(npe ?AI1) \
      "Anemómetro: "         $(npe ?AI2) \
      "Célula temperatura: " $(npe ?AI3) \
      "Célula temperatura: " $(npe ?AI4)
done
2
  • 10
    This needs modified to either print in a fixed width format, or clear the values before writing new ones (tput can do that as well). Otherwise if a value drops in size by a digit, the final digit from the previous value will not be overwritten, causing the number seen to be wrong. Nov 14, 2019 at 18:12
  • Good point. I'm on my phone now so can't test but I'll edit. Nov 14, 2019 at 20:34
13

It might be tricky to implement a real time solution in bash.

There are many ways to run script once in X seconds you can use watch. I assume you already have myScript.sh available. Replace X with number of seconds you need.

  1. watch -n X ./myScript.sh

  2. while sleep X; do ./myScript.sh; done

    upd. to emulate watch you might want to clear the screen in between iterations. inside the script it will look this way:

    while sleep X; do 
       clear; 
       command1;
       command2;
    done
    
  3. add one of options above to the script itself.

7
  • The watch command is what I need, as it keeps the output on the same line updated! Hence my question: if I could do the same without using external tools; directly integrated such functionality into my script. Nov 14, 2019 at 9:59
  • You could modify the second option to emulate watch. Just add clear inside the loop. I added it to my answer.
    – rush
    Nov 14, 2019 at 10:49
  • If I handle this the output of the script is illegible, they are blinking lines... Nov 14, 2019 at 11:22
  • I updated the post with more information and the problem I have with clear in the script Nov 14, 2019 at 11:32
  • 1
    @rush: you need to have the clear BEFORE the commands that displays results.. here: it display results, then immediately clear, then loop back to the sleep X (so the cleared screen is shown for X seconds). Nov 15, 2019 at 12:51
10

I am assuming the flicker is because your commands take a moment to return their values. This is my usual workaround:

cmds(){
  echo "Célula calibrada: " $(npe ?AI1);
  echo "Anemómetro: " $(npe ?AI2);
  echo "Célula temperatura: " $(npe ?AI3);
  echo "Célula temperatura: " $(npe ?AI4);
}

while true; do
  out="$(cmds)"
  clear
  echo "$out"
  sleep 1
done

The idea being that we clear the screen at the last possible moment.

5

If you clear immediately after the sleep, rather than immediately before, you'll be able to read the output more easily:

#!/bin/sh
while sleep 1
do
   clear
   echo "Célula calibrada: " $(npe ?AI1)
   echo "Anemómetro: " $(npe ?AI2)
   echo "Célula temperatura: " $(npe ?AI3)
   echo "Célula temperatura: " $(npe ?AI4)
done

However, I'd remove the loop here, and use watch to run the script repeatedly. That's a more flexible composition of individual tools.

1
  • This is my goal... that the output of the script is the same as the output with watch, and my question is whether this is possible without using wath or other external tools. Nov 14, 2019 at 11:37
3

You can do it exactly in bash. If the text is still flashing, you have not read the previous answers completely.

You have to clear the screen before you echo the new values, not after.

2
  • I updated the post with the new changes, but this only slightly improves the visualization, the blinking persists. Nov 14, 2019 at 12:21
  • If you are going to clear the screen every time, then it will be blank for a few milliseconds, the bottom longer than the top. I am doing 30 individual date commands per second, and the difference between line 1 and line 30 is 0.11 seconds. How many rows of output do you have? You could use tput commands to avoid clearing, and just modify the actual changed numbers. Minimise the window size to just fit the data, because clear has to wipe the whole window. Nov 15, 2019 at 14:37
2

you can put all your commands inside the while loop with some sleep timing. In the below example, i put sleep for every 2 seconds. So the date command will execute for every 2 seconds

#!/bin/bash

while (true)
do
        echo "Date & Time : $(date)"
        sleep 2
done
2
  • It's a good solution, but depending on several factors, there may be 2 or 20 commands, and throwing one line behind another with so many commands may make it uncomfortable to see... That's why I'm looking for a solution that keeps your own line up to date, without adding new ones. Nov 14, 2019 at 9:40
  • An example of this can be APT's status line, which updates its values on the same line: 35% [3 Packages 3,155 kB/9,540 kB 33% Nov 14, 2019 at 9:42
2

You are : displaying a line, then executing the command to display the next.

You need to : execute all the commands (without touching the display) and save the results in 4 vars, then clear and display those 4 results at once.

If I modify your own script, try :

#!/bin/bash
while sleep 1; do
    # facultative?#  tput cup 0 0
    ccab="$(npe ?AI1)"
    cane="$(npe ?AI2)"
    ctemp="$(npe ?AI3)"
    ctemp2="$(npe ?AI4)"
    clear
    printf "%21s %6d    \n" \
      "Célula calibrada: "   "$ccab" \
      "Anemómetro: "         "$cane" \
      "Célula temperatura: " "$temp" \
      "Célula temperatura: " "$temp2"
done

Here, the long execution of the 4 requests take place without changing the display, then the display is refreshed very quickly and displayed all at once with the 4 new values.

2

This does 50 lines without flickering, because it uses tput to update each line separately. I put in a sleep 0.01 between each row to prove this.

Running the script with arg y shows you examples of what your terminal window uses to position the cursor. I have hard-coded my escapes in the echo. Yours may be different. For portability, you should use tput to generate the sequences dynamically. For performance, you should get all the tput sequences you will ever need for your app up front, and store them in the shell. Also, once you have the fixed text in place once, just change the variable parts.

Note that screen positions start at (0, 0) in tput args. Also, leading zeros in screen commands are interpreted as octal, which is why I reformat the visible row number with printf. It is also polite to move the cursor out of the way (like to (0,0)) after updating.

#! /bin/bash

#.. See what a CUrsor Position is on the current terminal.
[[ "${1}" == y ]] && {
    for r in {0..19}; do
        tput cup ${r} 15 | od -An -t ax1
    done
    exit
}

tput clear

while sleep 1; do
    for k in {1..50}; do
        sleep 0.01
        RW=$( printf '%.2d' ${k} )
        TS=$(date "+%Y-%m-%d %H:%M:%S.%N")
        echo -n -e "\e[${k};1H${RW}  ${TS}"
    done
    echo -n -e "\e[52;1H"
done
1
  • Remove the "in situ" -- it's a typo that should be at the end of the first sentence. Nov 15, 2019 at 23:16
1

You can set up a loop in bash -- construct is while true; do ....; done where .... is your commands. You would need to Ctrl+C to stop it.

You need to put in a sleep in the loop, or it will just scream round and eat all your CPU time. The seconds to sleep is a compromise between getting current figures and having too much output.

If you format the output width-ways across the screen in fixed columns (use printf command), then it will be easier to see variations.

I would probably send the output from all that to a file (redirection after the "done"), so you can examine it at your leisure afterwards, maybe even graph it or do statistics. You can run a tail -f command in another window, which will show you the latest data being added to the file so you can see it as it happens too.

You can also put a date command in the loop to timestamp each line, in case the intervals between data are important.

Post again if any of that needs more meat on the bones.

1

After the expiry of sleep, echo a backspace and redisplay a value. This assumes you are on a terminal which can display escape codes, the width and lines of the terminal, but it works as a quick shot. Example: Display Spinner while waiting for some process to finish

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .