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

10 Answers10

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
glenn jackman
  • 85,964
  • 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. – Paul Sinclair Nov 14 '19 at 18:12
  • Good point. I'm on my phone now so can't test but I'll edit. – glenn jackman Nov 14 '19 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.

rush
  • 27,403
  • 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. – Fco Javier Balón Nov 14 '19 at 09: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 '19 at 10:49
  • If I handle this the output of the script is illegible, they are blinking lines... – Fco Javier Balón Nov 14 '19 at 11:22
  • I updated the post with more information and the problem I have with clear in the script – Fco Javier Balón Nov 14 '19 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). – Olivier Dulac Nov 15 '19 at 12:51
  • @OlivierDulac good point. fixed it. thx. – rush Nov 15 '19 at 13:33
  • watch -n 0.1 [command] make it funnier. As an example: watch -n 0.1 df -m that will shows you the real-time storage size monitoring. – M. Rostami Sep 23 '20 at 09:13
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.

bxm
  • 4,855
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.

Toby Speight
  • 8,678
  • 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. – Fco Javier Balón Nov 14 '19 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.

Paul_Pedant
  • 8,679
  • I updated the post with the new changes, but this only slightly improves the visualization, the blinking persists. – Fco Javier Balón Nov 14 '19 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. – Paul_Pedant Nov 15 '19 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
Kamaraj
  • 4,365
  • 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. – Fco Javier Balón Nov 14 '19 at 09: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% – Fco Javier Balón Nov 14 '19 at 09: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
Paul_Pedant
  • 8,679
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.

Eliah Kagan
  • 4,155
Paul_Pedant
  • 8,679
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