Formatting with printf linux - linux

I have a script below,
count=0
max=5
for i in {1..5}
do
sleep 1
count=`expr $count + 1`
printf "%s\r" "$i/$max completed"
done
echo "...OK"
The output of above script is below
(1 to 5 changing in place)/5 completed. then the line is replaced like "...OKompleted"
Output which I want is
(1 to 5 changing in place)/5...OK
Any suggestions on how to achieve this?
OS: CentOS7

\r only puts you at the beginning of the line. If you want to erase the line, you'll need some ANSI escape sequence, or you just use spaces to overwrite those characters.

You can try this:
count=0
max=5
for i in {1..5}
do
sleep 1
count=`expr $count + 1`
echo -en "\\r$i/$max completed"
done
echo "...OK"

Related

Show progress while sleep in bash

I wrote a simple script which must show progress while user waiting. But I get infinitive loop and seems sleep not working. What wrong in this code?
#!/bin/bash
spinner=(
"Working "
"Working. "
"Working.. "
"Working... "
"Working...."
)
while sleep 10
do
for item in ${spinner[*]}
do
echo -en "\r$item"
sleep .1
echo -en "\r \r"
done
done
One idea:
using the bash (system) variable SECONDS to measure our 10 seconds
using a tput code for ovewriting a line
eliminating the spinner[] array (since the only difference in values is the number of trailing periods)
EraseToEOL=$(tput el)
max=$((SECONDS + 10)) # add 10 seconds to current count
while [ $SECONDS -le ${max} ]
do
msg='Waiting'
for i in {1..5}
do
printf "%s" "${msg}"
msg='.'
sleep .1
done
printf "\r${EraseToEOL}"
done
printf "\n"
A small change to OP's current code using the max/SECONDS approach:
spinner=(
"Working "
"Working. "
"Working.. "
"Working... "
"Working...."
)
max=$((SECONDS + 10))
while [[ ${SECONDS} -le ${max} ]]
do
for item in ${spinner[*]}
do
echo -en "\r$item"
sleep .1
echo -en "\r \r"
done
done
Use the in/decrement variable i to put out the array...
#!/bin/bash
countdown(){
spinner=(
"Working "
"Working. "
"Working.. "
"Working... "
"Working...."
)
i=4
if [ ${i} -lt 5 ]
then
while true
do
for i in ${i}
do
printf "%s \t" ${spinner[i]}
sleep .1
printf "\r"
sleep .1
if [ ${i} -eq 0 ]
then
# Here you can clean up or do what to do at zero count
printf "\n"
unset i
unset spinner
return 0 # Can be used in ${?} from parent bash
else
i=$((${i}-1))
fi
done
done
return 1 # Should never be executed
fi
}
# A funny cd ;-)
cd(){
countdown && printf "%s\n" "DONE changing to "${1} # Gives out if return is 0 (${?})
unset cd
cd ${1}
}
#
cd ~
My method of showing progress while sleeping in bash:
sleep 5 | pv -t
It probably can't get any simpler than that :)
Check out this spiner
Or from this project

linux printf to specified terminal line

In yocto, when I use bibake to build one recipe, stages related with this recipe will be printed in multiple lines, very beautiful.
So I want implement a tiny example like it.
get current cursor's row
run 2 threads, one outputs something at row + 1, another outputs
something at row + 2
As a result, I failed. Then I run 2 process which do same things, failed again.
Can some one give me some suggestions?
What I want is like:
ts:/home/test$ ./program1 &; ./program1 &
program1's output.....................
program2's output.....................
In Bash:
#!/usr/bin/env bash
printf '\n\n'
echo -ne "\033[6n"
read -rs -d\[ _
read -rs -dR foo
cursor_pos=$(cut -d";" -f1 <<< "$foo")
upper_row=$((cursor_pos - 2))
lower_row=$((cursor_pos - 1))
echo -ne "\033[${upper_row};0H"
echo upper row
sleep 2
echo -ne "\033[${lower_row};0H"
echo lower low
sleep 2
echo -ne "\033[${upper_row};0H"
printf "\r\e[0K%s\n" "upper again"
sleep 2
echo -ne "\033[${lower_row};0H"
printf "\r\e[0K%s\n" "lower again"

bash: How to echo strings at the same position

I built my web server and I'm trying to do a test. So I simulate many requests with bash script:
i=0
while [ $i -lt 20 ]; do
echo ''
echo ''
echo ''
echo '============== current time ==============='
echo $i
echo '==========================================='
echo ''
curl -i http://www.example.com/index?key=abceefgefwe
i=$((i+1))
done
This works well but I prefer to make all of echo at the same position on the terminal.
I've read this: How to show and update echo on same line
So I add -ne for echo but it doesn't seem to work as expected.
The messages of curl can still push the echo away.
This is what I need:
============== current time =============== ---\
1 <------ this number keeps updating ----> the 3 lines stay here
=========================================== ---/
Here is the messages of `curl`, which are showing as normal way
There's another option, to position the cursor before you write to stdout.
You can set x and y to suit your needs.
#!/bin/bash
y=10
x=0
i=0
while [ $i -lt 20 ]; do
tput cup $y $x
echo ''
echo ''
echo ''
echo '============== current time ==============='
echo $i
echo '==========================================='
echo ''
curl -i http://www.example.com/index?key=abceefgefwe
i=$((i+1))
done
You could add a clear command at the beginning of your while loop. That would keep the echo statements at the top of the screen during each iteration, if that's what you had in mind.
When I do this sort of thing, rather than using curses/ncurses or tput, I just restrict myself to a single line and hope it doesn't wrap. I re-draw the line every iteration.
For example:
i=0
while [ $i -lt 20 ]; do
curl -i -o "index$i" 'http://www.example.com/index?key=abceefgefwe'
printf "\r==== current time: %2d ====" $i
i=$((i+1))
done
If you're not displaying text of predictable length, you might need to reset the display first (since it doesn't clear the content, so if you go from there to here, you'll end up with heree with the extra letter from the previous string). To solve that:
i=$((COLUMNS-1))
space=""
while [ $i -gt 0 ]; do
space="$space "
i=$((i-1))
done
while [ $i -lt 20 ]; do
curl -i -o "index$i" 'http://www.example.com/index?key=abceefgefwe'
output="$(head -c$((COLUMNS-28))) "index$i" |head -n1)"
printf "\r%s\r==== current time: %2d (%s) ====" "$space" $i "$output"
i=$((i+1))
done
This puts a full-width line of spaces to clear the previous text and then writes over the now-blank line with the new content. I've used a segment of the first line of the retrieved file up to a maximum of the line's width (counting the extra text; I may be one off somewhere). This would be cleaner if I could just use head -c$((COLUMNS-28)) -n1 (which would care about the order!).

BASH printing 2 strings on the same 2 lines

I'm rather new to BASH and I was wondering how could I print 2 strings on the same 2 lines.
What I'm trying to do, is create a 2 line progress-bar in BASH.
Creating 1 line progress bar is rather easy, I do it like this:
echo -en 'Progress: ### - 33%\r'
echo -en 'Progress: ####### - 66%\r'
echo -en 'Progress: ############ - 100%\r'
echo -en '\n'
But now I'm trying to do the same thing but with 2 lines, and everything I tried failed so far.
In the second line, I want to put a "Progress Detail" that tells me at what point in the script it is, like for example: what variable is gathering, what function is it running. But I just can't seem to create a 2 line progress bar.
It's possible to overwrite double lines using tput and printf, for example:
function status() {
[[ $i -lt 10 ]] && printf "\rStatus Syncing %0.0f" "$(( i * 5 ))" ;
[[ $i -gt 10 ]] && printf "\rStatus Completing %0.0f" "$(( i * 5 ))" ;
printf "%% \n" ;
}
for i in {1..20}
do status
printf "%0.s=" $(seq $i) ;
sleep .25 ; tput cuu1 ;
tput el ;
done ; printf "0%%\n" ; printf " %.0s" {1..20} ; printf "\rdone.\n"
one-liner:
for i in {1..20}; do status ; printf "%0.s=" $(seq $i) ; sleep .25 ; tput cuu1 ; tput el ; done ; printf "0%%\n" ; printf " %.0s" {1..20} ; printf "\rdone.\n"
The loop calls the status function to display the appropriate text during a particular time.
The resulting output would be similar to:
Status Completing 70%
==============
You can use \033[F to go to previous line, and \033[2K to erase the current line (just in case your output length changes).
That's the script I did:
echo -en 'Progress: ### - 33%\r'
echo -en "\ntest" # writes progress detail
echo -en "\033[F\r" # go to previous line and set cursor to beginning
echo -en 'Progress: ####### - 66%\r'
echo -en "\n\033[2K" # new line (go to second line) and erase current line (aka the second one)
echo -en "test2" # writes progress detail
echo -en "\033[F\r" # go to previous line and set cursor to beginning
echo -en 'Progress: ############ - 100%\r'
echo -en "\n\033[2K" # new line and erase the line (because previous content was "test2", and echoing "test" doesn't erase the "2")
echo -en "test" # write progress detail
echo -en '\n'

Integer addition in shell

Here is my simple shell code. I want the result to be 2.Shell treats everything as a string.
How can i get this done ?
num=1
num=$(( $num + 1 ))
EDIT :
Complete code : Whats wrong in this if i want to print from 1 to 10 ?
#! /bin/bash
num=1
until test $num -eq 10
do
num=$(( $num + 1 ))
echo $num
done
In bash, you don't need to do anything special:
aix#aix:~$ num=1
aix#aix:~$ num=$(( $num + 1 ))
aix#aix:~$ echo $num
2
#tonio; please don't advocate using subshell (` ... or $( ... ) ) constructs when they're not needed (to keep confusion to the maximum, $(( ... )) is not a sub-shell construct). Sub-shells can make a tremendous performance hit even with rather trivial amounts of data. The same is true for every place where an external program is used to do somethign that could be done with a shel built-in.
Example:
num=1
time while [[ $num -lt 10000 ]]; do
num=$(( num+1 ))
done
echo $num
num=1
time while /bin/test $num -lt 10000; do
num=$( /bin/expr $num + 1 )
done
echo $num
Output (run in ksh on Linux):
real 0m0.04s
user 0m0.04s
sys 0m0.01s
10000
real 0m20.32s
user 0m2.23s
sys 0m2.92s
10000
...so run-time factor of 250, and CPU-time factor of 100. I admit the example I used was a exaggerated one, with explicitly requiring all built-ins to be bypassed, but I think the point was made: creating new processes is expenisve, avoid it when you can, and know your shell to recognise where new processes are created.
This might work for you:
num=1; ((num++)); echo $num
2
or
num=1; echo $((++num))
2
for loops
for num in {1..10}; do echo $num; done
or (in bash at least)
for ((num=1; num<=10; num++)) { echo $num; }
second loop more useful when more programming involved:
for (( num=1,mun=10; num<=10; num++,mun--)) { echo $num $mun; }
You just did:
$ num=1; num=$(( $num + 1 ));echo $num
2
Note: You don't need to quote variables inside $(( )). Also, you can just use $((num++))
You are not specifying which shell you are using, but the most concise form I know is this one (works at least in bash):
num=$[num+1]
If only incrementing by one and changing the variable itself rather than printing/assigning, then:
((num++))
Is a better/more elegant solution. See dogbane's answer for that.
If looping over the values, I would use this form instead:
for i in `seq 1 10`; do
echo $i
done
Use ((num++)) as shorthand for incrementing num.
$ num=1
$ ((num++))
$ echo $num
2
try this
$ num=1; num=`expr $num + 1`; echo $num;
EDIT:
More efficient would be:
num=$(( num + 1 ))
Thanks #Charles Duffy for your comment.
works for me
$ num=1
$ num=$(( $num + 1 ))
$ echo $num
2
What output do you get?
Read more about bash Arithmetic # tldp
EDIT
To do something 10 times in bash you can use (using brace-expansion}
$ for i in {1..10}; do echo $i; done
1
2
3
4
5
6
7
8
9
10
However, you cannot use variables between the {}. If this is the case, use seq instead.

Resources