bash script overwrite current line completely - bash

I have been playing with a script to test the speed of various VoIP servers by pinging, I then found a progress bar script and incorporated that... because cool!
now I'm trying to display the current server being tested below the status bar, I have the line overwriting but if the next server name is shorter it does not overwrite completely. I have tried various suggestions I've found but non work or they screw up my progress bar.
Im running osx but may also use this on various linux distros.
any suggestions would be great!
#!/bin/bash
HOSTS=("atlanta.voip.ms" "atlanta2.voip.ms" "chicago.voip.ms" "chicago2.voip.ms" "chicago3.voip.ms" "chicago4.voip.ms" "dallas.voip.ms" "denver.voip.ms" "denver2.voip.ms" "houston.voip.ms" "houstonnew1.voip.ms" "houstonnew2.voip.ms" "losangeles.voip.ms" "losangeles2.voip.ms" "newyork.voip.ms" "newyork2.voip.ms" "newyork3.voip.ms" "newyork4.voip.ms" "sanjose.voip.ms" "sanjose2.voip.ms" "seattle.voip.ms" "seattle2.voip.ms" "seattle3.voip.ms" "tampa.voip.ms" "tampanew1.voip.ms" "tampanew2.voip.ms" "washington.voip.ms" "washington2.voip.ms" "montreal.voip.ms" "montreal2.voip.ms" "montreal3.voip.ms" "montreal4.voip.ms" "toronto.voip.ms" "toronto2.voip.ms" "toronto3.voip.ms" "toronto4.voip.ms" "vancouver.voip.ms" "vancouver2.voip.ms" "amsterdam.voip.ms" "london.voip.ms" "melbourne.voip.ms" "paris.voip.ms")
Smallest="200000"
Server=""
tLen=${#HOSTS[#]}
# Slick Progress Bar
# Created by: Ian Brown (ijbrown#hotmail.com)
# Please share with me your modifications
# Functions
PUT(){ echo -en "\033[${1};${2}H";}
DRAW(){ echo -en "\033%";echo -en "\033(0";}
WRITE(){ echo -en "\033(B";}
HIDECURSOR(){ echo -en "\033[?25l";}
NORM(){ echo -en "\033[?12l\033[?25h";}
function showBar {
percDone=$(echo 'scale=2;'$1/$2*100 | bc)
halfDone=$(echo $percDone/2 | bc)
barLen=$(echo ${percDone%'.00'})
halfDone=`expr $halfDone + 6`
tput bold
PUT 7 28; printf "%4.4s " $barLen%
PUT 5 $halfDone; echo -e "\033[7m \033[0m"
tput sgr0
}
# Start Script
clear
HIDECURSOR
echo -e ""
echo -e ""
DRAW
echo -e " PLEASE WAIT WHILE SCRIPT IS IN PROGRESS"
echo -e " lqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqk"
echo -e " x x"
echo -e " mqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqj"
echo -e ""
echo -e ""
WRITE
for (( i=0; i<${tLen}; i++ ))
do
showBar $i ${tLen}
serl=${HOSTS[$i]}
seru=$(echo "$serl" | tr '[:lower:]' '[:upper:]')
echo ""
echo ""
echo ""
echo ""
echo ""
echo ""
echo ""
#this line needs to overwrite completley
echo -ne "" '\r " TESTING:" $seru
Current1=` ping -c 4 -q -i .2 ${HOSTS[$i]} | grep avg | awk -F'/' '{print $5 }'`
Current=${Current1/./}
if [ "$Current" -lt "$Smallest" ]
then
Server=${HOSTS[$i]}
Smallest=$Current
fi
done
clear
Smallestd=$(echo "$Smallest" | sed 's/...$/.&/')
echo "Fastest Server = $Server # $Smallestd ms"

Here is an example of controlling clearing to end-of-line both during display of the meter, and in restoring the cursor after completion. I have reversed the loop to show the meter progressing from 100% (full) to 1% cleaning up after itself as it goes:
#!/bin/bash
## string of characters for meter (60 - good for 120 char width)
str='▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒'
tput civis # make cursor invisible
for i in `seq 100 -1 1`; do # for 1 to 100, save cursor, restore, output, restore
printf "\033[s\033[u Progress: %s %3d %% \033[u" "${str:0:$(((i+1)/2))}" "$i"
sleep 0.1 # small delay
done
sleep 0.5
printf "\033[K" # clear to end-of-line
tput cnorm # restore cursor to normal
exit 0
Note: cursor control and clearing during display of the meter is provided by the ANSI escapes for save cursor position and restore cursor position. After completion, clear to end of line is used to clean up. tput is used to control cursor visibility, but can also be used to save, restore and clear to end of line.

You can pad your with spaces so you always write the same number of characters. That way you'll overwrite the extra characters from before with spaces. For example you could something like
echo -en "\r"; echo -n $(printf " TESTING: %-40s" $seru)

Thanks to David C. Rankin I have an easy working answer
echo -ne "" '\r' " TESTING:" $seru '\033[K'

Related

update value in bash script

i have stuck code. i make bash script to send Notification If file Size changed
#!/bin/bash
#File Embeded
test_file=/home/optimus/bot_test/dump.txt
msg_caption=/tmp/telegram_msg_caption.txt
#initialize
initCount=0
#checkLog
#cmd="ls /var/log/snort/* | wc -l"
cmd=$(wc -c "dump.txt" | awk '{print $1}')
#Chat ID and TOKEN Telegram
chat_id="xxxx"
token="xxx"
#Send Alert
function sendAlert
{
curl -s -F chat_id=$chat_id -F caption="$caption" -F document=#$test_file https://api.telegram.org/bot$token/sendDocument > /dev/null 2&>1
}
#Monitoring DoS Server
while true
do
#GetLastCount
echo "Start Execute"
lastCount=$cmd
echo before_last $lastCount #ex 100 #after reset 0
echo before_init $initCount #ex 0
echo "--------------------"
if(($(($lastCount)) > $initCount));
then
echo "Running Script..." #debug
echo -e "Halo Admin\nTerjadi Indikasi Penyerangan DoS!!!" > $msg_caption #set Caption / Pesan
caption=$(<$msg_caption) #set Caption
sendAlert #Panggil Fungsi
#error disini
initCount=$lastCount
lastCount=0 #reset
echo after_last $lastCount
echo after_Init $initCount
echo "==================="
rm -f $msg_caption
sleep 5 #delay Proses jika ada indikasi
fi
sleep 5 #delay proses jika tidak ada indikasi
done
when code first running, send First Notification, and if file size changed , value in code not updated
example i use dump.txt to object size changed
how to get Last Size from dump.txt after
echo "Start Execute"
conclusion every 5 second, lastCount get last size File updated
The problem in your code is that the cmd=$(wc -c "dump.txt" | awk '{print $1}') line executes only once.
Instead, you should use:
#....
do
#...
lastCount=$(wc -c "dump.txt" | awk '{print $1}')
#...
Or make the cmd a function or store in a string and evaluate the string. The way you wrote, it is only evaluated in the beginning of your script and you use the same value afterward.

How to detect a non-rolling log file and pattern match in a shell script which is using tail, while, read, and?

I am monitoring a log file and if PATTERN didn't appear in it within THRESHOLD seconds, the script should print "error", otherwise, it should print "clear". The script is working fine, but only if the log is rolling.
I've tried reading 'timeout' but didn't work.
log_file=/tmp/app.log
threshold=120
tail -Fn0 ${log_file} | \
while read line ; do
echo "${line}" | awk '/PATTERN/ { system("touch pattern.tmp") }'
code to calculate how long ago pattern.tmp touched and same is assigned to DIFF
if [ ${diff} -gt ${threshold} ]; then
echo "Error"
else
echo "Clear"
done
It is working as expected only when there is 'any' line printed in the app.log.
If the application got hung for any reason and the log stopped rolling, there won't be any output by the script.
Is there a way to detect the 'no output' of tail and do some command at that time?
It looks like the problem you're having is that the timing calculations inside your while loop never get a chance to run when read is blocking on input. In that case, you can pipe the tail output into a while true loop, inside of which you can do if read -t $timeout:
log_file=/tmp/app.log
threshold=120
timeout=10
tail -Fn0 "$log_file" | while true; do
if read -t $timeout line; then
echo "${line}" | awk '/PATTERN/ { system("touch pattern.tmp") }'
fi
# code to calculate how long ago pattern.tmp touched and same is assigned to diff
if [ ${diff} -gt ${threshold} ]; then
echo "Error"
else
echo "Clear"
fi
done
As Ed Morton pointed out, all caps variable names are not a good idea in bash scripts, so I used lowercase variable names.
How about something simple like:
sleep "$threshold"
grep -q 'PATTERN' "$log_file" && { echo "Clear"; exit; }
echo "Error"
If that's not all you need then edit your question to clarify your requirements. Don't use all upper case for non exported shell variable names btw - google it.
To build further on your idea, it might be beneficial to run the awk part in the background and a continuous loop to do the checking.
#!/usr/bin/env bash
log_file="log.txt"
# threshold in seconds
threshold=10
# run the following process in the background
stdbuf -oL tail -f0n "$log_file" \
| awk '/PATTERN/{system("touch "pattern.tmp") }' &
while true; do
match=$(find . -type f -iname "pattern.tmp" -newermt "-${threshold} seconds")
if [[ -z "${match}" ]]; then
echo "Error"
else
echo "Clear"
fi
done
This looks to me like a watchdog timer. I've implemented something like this by forcing a background process to update my log, so I don't have to worry about read -t. Here's a working example:
#!/usr/bin/env bash
threshold=10
grain=2
errorstate=0
while sleep "$grain"; do
date '+[%F %T] watchdog timer' >> log
done &
trap "kill -HUP $!" 0 HUP INT QUIT TRAP ABRT TERM
printf -v lastseen '%(%s)T'
tail -F log | while read line; do
printf -v now '%(%s)T'
if (( now - lastseen > threshold )); then
echo "ERROR"
errorstate=1
else
if (( errorstate )); then
echo "Recovered, yay"
errorstate=0
fi
fi
if [[ $line =~ .*PATTERN.* ]]; then
lastseen=$now
fi
done
Run this in one window, wait $threshold seconds for it to trigger, then in another window echo PATTERN >> log to see the recovery.
While this can be made as granular as you like (I've set it to 2 seconds in the example), it does pollute your log file.
Oh, and note that printf '%(%s)T' format requires bash version 4 or above.

Center Justify/align ASCII art in bash

I was just working on my bash project and
I want the header ascii art to automatically adjust at the center whenever the terminal executes the script in whatever resolution. Is it possible mates?
Following is my code:
#!/bin/bash
clear
echo
echo -e "\t\t1▄██████▄#0000▄████████0000▄████████11▄██████▄ ";
echo -e "\t\t███0000███111███0110███111███1011███1███#0000██";
echo -e "\t\t███0001███111███0111███111███1100█▀11███#ffff██";
echo -e "\t\t███0010███11▄███▄▄▄▄██▀11▄███▄▄▄11111███#0000██";
echo -e "\t\t███0011███1▀▀███▀▀▀▀▀111▀▀███▀▀▀11111███#ffff██";
echo -e "\t\t███0100███1▀███████████111███1101█▄11███#0000██";
echo -e "\t\t███0101███111███1000███111███1110███1███#ffff██";
echo -e "\t\t1▀██████▀ffff███1001███111██████████11▀██████▀1";
echo -e "\n\n"
You can use the COLUMNS environment variable that returns the width of the terminal.
banner_width=46
indent=$(( (COLUMNS - banner_width) / 2 ))
prefix=''
for ((i=1; i<=indent; i++)) ; do
prefix+=' '
done
echo
echo -e "${prefix}1▄██████▄#0000▄████████0000▄████████11▄██████▄ ";

Shell script output, how to scroll up?

I have a shell script that runs on Debian. It contains lots of functions and conditions. There is a menu and the user may choose different options and different outputs will be displayed. The user can always go back to the main menu and choose again some options then another output will be displayed. Of course each time the screen is cleared with "clear".
However when then output contains too many lines, I will be able to scroll up a little bit, but it will stop and I won't be able to scroll all the way to the first line I need to see. Being able to scroll up with the mousse wheel is the king of behavior I would like...
It looks like the problem comes from the xterm window, because it is fine with the normal terminal. However xterm is nice because I can setup the height and the width, as well as changing the colors...
Is there a way to increase this limitation from the script itself as I won't have the permission to change anything in the Debian environment...
I read that some people actually pipe the entire script to "less", I tried that, the problem is that I can't use the menu anymore...
Please find below the first script that is used to run the main one:
xterm -fg ivory -bg darkblue -fn 8x13bold -geometry 76x110+1700+0 -T "QC CHECK" -e /tests/SCRIPTS/QC/qc.sh
Below is a little sample of my script, but, it contains much more:
#!/bin/sh
stty erase ^H
function water
{
clear
echo -e "Current Survey : ${proj}"
echo -e "Current Sequence : ${seq}"
echo -e ""
echo -e " [ Trace QC Water Column ]"
echo -e ""
if [ $mincable -eq $maxcable ]
then
echo -e " Cable checked : $maxcable"
else
echo -e " Cables checked : ${mincable}-${maxcable}"
fi
echo -e " Max noise level : ${maxnoise}uB"
if [ ${skiptrace} -eq 0 ]
echo -e " Traces skipped : ${skiptrace}"
else
echo -e " Traces skipped : 1-${skiptrace}"
fi
echo -e ""
#############
water=`awk --field-separator=";" '($4>'$maxnoise') {print int(a=(($1-1)/'$nb_traces')+1) " " ($1-((int(a)-1)*'$nb_traces')) " " $4}' ${seq}_TraceAverages.txt | grep -v "USER_AVRMS_WC1" | grep -v "R32" | awk '{printf $1 " " $2 " " ("%*.*f\n"), 1, 2, $3}' | awk '($2>'$skiptrace')&&($1>='$mincable')&&($1<='$maxcable') {print $1 " - " $2 " - " $3}' | awk '{printf("%16s%6s%8s%6s%10s\n"), $1, $2, $3, $4, $5}' | awk '(NR>1) && (old != $1) {printf("%65s\n"), "'$sep_cable'"} {print; old=$1}'`
#############
count_water=`awk --field-separator=";" '($4>'$maxnoise') {print int(a=(($1-1)/'$nb_traces')+1) " " (b=($1-((int(a)-1)*'$nb_traces'))) " " $3}' ${seq}_TraceAverages.txt | grep -v "USER_AVRMS_WC1" | grep -v "R32" | awk '($2>'$skiptrace')&&($1>='$mincable')&&($1<='$maxcable') {print $3}' | wc -l`
#############
echo -e " ------------------------------------------------------"
echo -e ""
echo -e " Cable - Trace - RMS_WC"
echo -e ""
echo -e " ------------------------------------------------------"
echo -e ""
if [ $count_water -ge 1 ]
then
echo -e "$water"
else
setterm -term linux -back red -fore white
echo -e " Wow! No traces? Maybe decrease your values..."
setterm -term linux -default
fi
echo -e ""
setterm -term linux -back blue -fore white
echo -e " RMS_WC > ${maxnoise}uB = $count_water"
setterm -term linux -default
echo -e ""
echo -e " ------------------------------------------------------"
echo -e ""
}
# check for latest project in /tests
proj_temp
# if config file is missing go to config menu
if [ ! -e /tests/$proj/SCRIPTS/QC/config ]
then
config
fi
# force choice=1 and config_ok=1 to return to main menu when loop has run once (no problem when more than one)
choice=1
config_ok=1
while :
do
# do it all
if [ ${choice} -eq 1 2>/dev/null ]
then
choice=X
config_ok=1
# read configuration file
readconfig
main
# config menu and help
if [ ${seq} = "c" ]
then
config
elif [ ${seq} = "h" ]
then
help
elif [ ${seq} = "q" ]
then
clear
setterm -term linux -back magenta -fore white
echo ""
echo -e "\t Try me next time :*"
sleep 0.65
exit
fi
# config_ok=1 when configuration is done, meaning user returns to main menu after exiting config menu
if [ ${config_ok} -eq 1 ]
then
cd $input_dir
# check if file for requested sequence is valid
testline
# this function updates the awk script for signal QC check
awkscript
doall
choice
fi
# let the user choose what QC is wanted
elif [ ${choice} -eq 2 2>/dev/null ]
then
choice=X
# initialize values so that user can choose its own
init
menu
option
fi
case ${menu} in
1) deep
choice ;;
2) water
choice ;;
3) awkscript
signal
choice ;;
4) readconfig
awkscript
doall
choice ;;
esac
if [ ${choice} = "q" 2>/dev/null ]
then
exit
fi
done
As you can see I have many functions and many variables that are called is some "echo" which makes it hard for me to scroll up when there is too many lines, and, the user got also to scroll up and down to see everything and to choose and action.
Pipe the output thru less or more. There are options (hot keys) to go forth, back, search etc.
I could not find a way to scroll through the length of my output so what I did is a loop that is gradually increasing by increments of 0.1 the value of ${maxnoise} (with a condition on the number of line output) because this variable is actually the one conditioning how big is the output. It works fine this way so I consider my question answered.

Bash variable scope

Please explain to me why the very last echo statement is blank? I expect that XCODE is incremented in the while loop to a value of 1:
#!/bin/bash
OUTPUT="name1 ip ip status" # normally output of another command with multi line output
if [ -z "$OUTPUT" ]
then
echo "Status WARN: No messages from SMcli"
exit $STATE_WARNING
else
echo "$OUTPUT"|while read NAME IP1 IP2 STATUS
do
if [ "$STATUS" != "Optimal" ]
then
echo "CRIT: $NAME - $STATUS"
echo $((++XCODE))
else
echo "OK: $NAME - $STATUS"
fi
done
fi
echo $XCODE
I've tried using the following statement instead of the ++XCODE method
XCODE=`expr $XCODE + 1`
and it too won't print outside of the while statement. I think I'm missing something about variable scope here, but the ol' man page isn't showing it to me.
Because you're piping into the while loop, a sub-shell is created to run the while loop.
Now this child process has its own copy of the environment and can't pass any
variables back to its parent (as in any unix process).
Therefore you'll need to restructure so that you're not piping into the loop.
Alternatively you could run in a function, for example, and echo the value you
want returned from the sub-process.
http://tldp.org/LDP/abs/html/subshells.html#SUBSHELL
The problem is that processes put together with a pipe are executed in subshells (and therefore have their own environment). Whatever happens within the while does not affect anything outside of the pipe.
Your specific example can be solved by rewriting the pipe to
while ... do ... done <<< "$OUTPUT"
or perhaps
while ... do ... done < <(echo "$OUTPUT")
This should work as well (because echo and while are in same subshell):
#!/bin/bash
cat /tmp/randomFile | (while read line
do
LINE="$LINE $line"
done && echo $LINE )
One more option:
#!/bin/bash
cat /some/file | while read line
do
var="abc"
echo $var | xsel -i -p # redirect stdin to the X primary selection
done
var=$(xsel -o -p) # redirect back to stdout
echo $var
EDIT:
Here, xsel is a requirement (install it).
Alternatively, you can use xclip:
xclip -i -selection clipboard
instead of
xsel -i -p
I got around this when I was making my own little du:
ls -l | sed '/total/d ; s/ */\t/g' | cut -f 5 |
( SUM=0; while read SIZE; do SUM=$(($SUM+$SIZE)); done; echo "$(($SUM/1024/1024/1024))GB" )
The point is that I make a subshell with ( ) containing my SUM variable and the while, but I pipe into the whole ( ) instead of into the while itself, which avoids the gotcha.
#!/bin/bash
OUTPUT="name1 ip ip status"
+export XCODE=0;
if [ -z "$OUTPUT" ]
----
echo "CRIT: $NAME - $STATUS"
- echo $((++XCODE))
+ export XCODE=$(( $XCODE + 1 ))
else
echo $XCODE
see if those changes help
Another option is to output the results into a file from the subshell and then read it in the parent shell. something like
#!/bin/bash
EXPORTFILE=/tmp/exportfile${RANDOM}
cat /tmp/randomFile | while read line
do
LINE="$LINE $line"
echo $LINE > $EXPORTFILE
done
LINE=$(cat $EXPORTFILE)

Resources