Terminal: get rid of cursor flicker - terminal

I am printing to the same line over and over with a while loop to monitor progress.
echo; while true; do
sleep 0.1;
echo -en "\e[1A";
run | some |code | awk '{print}';
done;
Prints my output to the same line every time.
However, there is a buffer problem: the cursor flickers between the echo and the print statement.
How do I get rid of the terminal cursor flicker in my status while loop?
Perhaps a different question, but still a solution: how to temporarily suppress the cursor after a command?

You can reduce flicker by combining the echo into the awk command. For instance:
echo; while true; do
sleep 0.1;
run | some |code | awk '{printf("\033[A%s\n", $0); }';
done;
The (nonstandard) \e is equivalent to \033, and you do not really need the repeat-count 1.

Related

Unix bash script grep loop counter (for)

I am looping our the a grep result. The result contains 10 lines (every line has different content). So the loop stuff in the loop gets executed 10 times.
I need to get the index, 0-9, in the run so i can do actions based on the index.
ABC=(cat test.log | grep "stuff")
counter=0
for x in $ABC
do
echo $x
((counter++))
echo "COUNTER $counter"
done
Currently the counter won't really change.
Output:
51209
120049
148480
1211441
373948
0
0
0
728304
0
COUNTER: 1
If your requirement is to only print counter(which is as per shown samples only), in that case you could use awk(if you are ok with it), this could be done in a single awk like, without creating variable and then using grep like you are doing currently, awk could perform both search and counter printing in a single shot.
awk -v counter=0 '/stuff/{print "counter=" counter++}' Input_file
Replace stuff string above with the actual string you are looking for and place your actual file name for Input_file in above.
This should print like:
counter=1
counter=2
........and so on
Your shell script contains what should be an obvious syntax error.
ABC=(cat test.log | grep "stuff")
This fails with
-bash: syntax error near unexpected token `|'
There is no need to save the output in a variable if you only want to process one at a time (and obviously no need for the useless cat).
grep "stuff" test.log | nl
gets you numbered lines, though the index will be 1-based, not zero-based.
If you absolutely need zero-based, refactoring to Awk should solve it easily:
awk '/stuff/ { print n++, $0 }' test.log
If you want to loop over this and do something more with this information,
awk '/stuff/ { print n++, $0 }' test.log |
while read -r index output; do
echo index is "$index"
echo output is "$output"
done
Because the while loop executes in a subshell the value of index will not be visible outside of the loop. (I guess that's what your real code did with the counter as well. I don't think that part of the code you posted will repro either.)
Do not store the result of grep in a scalar variable $ABC.
If the line of the log file contains whitespaces, the variable $x
is split on them due to the word splitting of bash.
(BTW the statement ABC=(cat test.log | grep "stuff") causes a syntax error.)
Please try something like:
readarray -t abc < <(grep "stuff" test.log)
for x in "${abc[#]}"
do
echo "$x"
echo "COUNTER $((++counter))"
done
or
readarray -t abc < <(grep "stuff" test.log)
for i in "${!abc[#]}"
do
echo "${abc[i]}"
echo "COUNTER $((i + 1))"
done
you can use below increment statement-
counter=$(( $counter + 1));

How to run a script in the background then when a condition is met, return it to foreground BASH

I want to run a script in the background, and when the time comes bring it back from background and call another script that will take in user input from the command-line, when I use this code, it's able to run the script effectively, but when it calls the other script, it only prints out a couple lines and It isn't able to take in user input, just straight up exits out. Been at it for a couple hours, no idea what to do from here.
I call this script with ./runTool.sh &
currTime=$(ls -lu | grep test01 | awk '{print $8}')
currHour="${currTime:0:2}"
currMin="${currTime:3:3}"
check=0
while [ true ]
do
timestamp=$(ls -lu | grep test01 | awk '{print $8}')
timeHour="${timestamp:0:2}"
timeMin="${timestamp:3:3}"
if (( $timeHour > $currHour )) || (( $timeMin > $currMin )) || (($timeHour < $currHour ))
then
check=1
set -m
fg %1
./tool.sh
break
fi
sleep 1
done

displaying command output in stdout then save to file with transformation?

I have a long-running command which outputs periodically. to demonstrate let's assume it is:
function my_cmd()
{
for i in {1..9}; do
echo -n $i
for j in {1..$i}
echo -n " "
echo $i
sleep 1
done
}
the output will be:
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
I want to display the command output meanwhile save it to a file at the same time.
this can be done by my_cmd | tee -a res.txt.
Now I want to display the output to terminal as-is but save to file with a transformed flavor, say with sed "s/ //g".
so the res.txt becomes:
11
22
33
44
66
77
88
99
how can I do this transformation on-the-fly without waiting for command exits then read the file again?
Note that in your original code, {1..$i} is an error because sequences can't contain variables. I've replaced it with seq. Also, you're missing a do and a done for the inner for loop.
At any rate, I would use process substitution.
#!/usr/bin/env bash
function my_cmd {
for i in {1..9}; do
printf '%d' "$i"
for j in $(seq 1 $i); do
printf ' '
done
printf '%d\n' "$j"
sleep 1
done
}
my_cmd | tee >(tr -d ' ' >> res.txt)
Process substitution usually causes bash to create an entry in /dev/fd which is fed to the command in question. The contents of the substitution run asynchronously, so it doesn't block the process sending data to it.
Note that the process substitution isn't a REAL file, so the -a option for tee is meaningless. If you really want to append to your output file, >> within the substitution is the way to go.
If you don't like process substitution, another option would be to redirect to alternate file descriptors. For example, instead of the last line in the script above, you could use:
exec 5>&1
my_cmd | tee /dev/fd/5 | tr -d ' ' > res.txt
exec 5>&-
This creates a file descriptor, /dev/fd/5, which redirects to your real stdout, the terminal. It then tells tee to write to this, allowing the normal stdout from tee to be processed by additional pipe elements before final redirection to your log file.
The method you choose is up to you. I find process substitution clearer.
Something you need to modify in your function. And you may use tee in the for loop to print and write file at the same time. The following script may get the result you desire.
#!/bin/bash
filename="a.txt"
[ -f $filename ] && rm $filename
for i in {1..9}; do
echo -n $i | tee -a $filename
for((j=1;j<=$i;j++)); do
echo -n " "
done
echo $i | tee -a $filename
sleep 1
done
Instead of double loop, I would use printf and its formatting capability %Xs to pad with blank characters.
Moreover I would use double printing (for stdout and your file) rather than using pipe and starting new processes.
So your function could look like this:
function my_cmd() {
for i in {1..9}; do
printf "%s %${i}s\n" $i $i
printf "%s%s\n" $i $i >> res.txt
done
}

How to display progress of another command

I need help with a bash script I run like this:
do_something > file.txt (I'm using the third line of this file.txt in another echo output)
Now I need to get a number of characters on the second line of the file.txt.
(There are only dots - ".")
I can get the number of characters with this command:
progress=$(awk 'NR==2' file.txt | grep -o \. | wc -w)
But the problem is, that the file.txt and the number of characters on the second line is "progress bar" so it's changing in time from 0 - XY (i.e. 100) characters.
I want to use it to see a progress in percentage: echo -ne "$progress % \\r"
How could I do that in a loop? do_something > file.txt must start just once. In next ~5-20 seconds it's printing dots on the second line and I have to take this number updated every second to my output echo "XY %".
How can I read from file.txt every second and find there "new/updated" count of characters?* < edit
edit:
* it's real-time process. My do_something > file.txt is "printing" dots to this file and I want print result saved in $progress in real-time. So first command is printing dots to file and I'm counting them in real-time every second and print how many percent is done from 0-100 %
What you want to do is run do_something > file.txt in the background and then monitor it. You can use the special kill signal 0 to do this.
do_something > file.txt &
PID=$!
while kill -0 $PID 2> /dev/null
do
[calculate percent complete]
[display percent complete]
sleep 5
done
First, you should run your command in the background:
do_something > file.txt &
Then you can watch the changes in the output file. This will infinitely print the second line of file.txt every second.
while true; do sed -n '2p' < file.txt; sleep 1; done
If you want to print only how many characters are on the second line, you can do this:
while true; do sed -n '2p' < file.txt | wc -m; sleep 1; done
If you want to stop when there is 100 characters on the second line, you can do this:
MAX="100"
CUR="0"
while [ $CUR -lt $MAX ]; do CUR=`sed -n '2p' < sprint | wc -m`; echo $CUR; sleep 1; done

Is it possible to have bash script output multiple lines over the same lines without 'clear'?

Bash script which outputs some stats.
while :
do
date
sensors | grep "temp1"
sensors | grep "Core"
acpi
sleep 1
done
It's possible to have one liner, like date and output it over and over the same line using echo -ne "$(date)\r". Is it possible to do same with more than one line without using clear?
One option is to run it under GNU watch
watch -n 1 'date; sensors | grep "temp1" ;sensors | grep "Core";acpi'
That's how you can do it:
while true; do
date
sensors | grep "temp1"
sensors | grep "Core"
acpi
sleep 1
for i in {1..4}; do # clear four lines above
tput cuu1 # up by one line
tput el # clear that line
done
done
Use man tput for more info. To see the list of capabilities use man terminfo
Edit:
Here is a hack that I came up with to avoid blinking:
while true; do
echo -n "$(date)"; tput el; echo
echo -n "$(sensors | grep "temp1")"; tput el; echo
echo -n "$(sensors | grep "Core")"; tput el; echo
echo -n "$(acpi)"; tput el; echo
sleep 1
tput cuu 4
# tput -S <<< $'cuu1 \n cuu1 \n cuu1 \n cuu1' # that's how you pass several actions to tput, but instaed of cuu1 several times use 'cuu N'
done
And of course, this is going to work only if your commands output only one line.
You could have:
echo -n $'\e[H\e[2J'
Or
tput clear

Resources