In bash loop script, how to refresh output in same line? (take the position of the last output) - bash

For example,
I have output every second like
user#pc:
A
B
C
D
E
I want the output refresh from A to E in the same line(no new lines being created)
Thanks!

You can use the carriage return character "\r". Check the difference between:
for x in $(seq 10); do printf "$x"; sleep 1; done
and
for x in $(seq 10); do printf "$x\r"; sleep 1; done

Depending upon where your input is coming from.
I would employ the below techniques.
For files:
#!/bin/bash
while read line
do
echo -e "\e[1A" # moving the cursor back to the previously printed line
sleep 1s;
echo -ne "$line\e[K" # \e[K cleans the residues of the previous output.
done <file_name
echo
For arrays:
#!/bin/bash
arr=(12 11 10 9 8 7 6 5 4 3 2 1 0)
for i in ${arr[#]}
do
echo -e "\e[1A"
sleep 1s;
echo -ne "Waiting time : "$i" Seconds\e[K"
done
echo

Related

Bash Script accepting a number, then printing a set of int from 0 to the number entered

I am trying to write a bash script that accepts a number from the keyboard, and then prints a set of integers from 0 to the number entered. I can't figure out how to do it at all.
This is my code:
while [ 1 ]
do
echo -n "Enter a color: "
read user_answer
for (( $user_answer = $user_answer; $user_answer>0; $user_answer--))
echo $user_answer
fi
done
exit
The error I'm recieving is:
number_loop: line 10: syntax error near unexpected token echo'
number_loop: line 10: echo $user_answer'
Assign a separate variable in order to use increment/decrement operators. $user_answer=$user_answer will always be true and it will throw an error when trying to use decrement. Try the following :
#!/bin/bash
while [ 1 ]
do
echo -n "Enter a color: "
read user_answer
for (( i=$user_answer; i>0; i-- ))
do
echo $i
done
done
exit
You missed the do statement between your for and the echo.
bash has many options to write numbers. What you seem to be trying to do is easiest done with seq:
seq $user_answer -1 0
If you want to use your loop, you have to insert a ; do and replace the fi with done, and replace several $user_answer:
for (( i = $user_answer; i>0; i--)); do
echo $i
done
(btw: I assumed that you wanted to write the numbers in reverse order, as you are going backwards in your loop. Forwards is even easier with seq:
seq 0 $user_input
)
This is where a c-style loop works particularly well:
#!/bin/bash
for ((i = 1; i <= $1; i++)); do
printf "%s\n" "$i"
done
exit 0
Example
$ bash simplefor.sh 10
1
2
3
4
5
6
7
8
9
10
Note: <= is used as the for loop test so it 10 it iterates 1-10 instead of 0-9.
In your particular case, iterating from $user_answer you would want:
for (( i = $user_answer; i > 0; i--)); do
echo $i
done
The for loop is a bash internal command, so it doesn't fork a new process.
The seq command has a nice, one-line syntax.
To get the best of the twos, you can use the { .. } syntax:
eval echo {1..$answer}

read line by line using `for` loop

How can I read line by line using for loop?
what I've tried:
hh=$(echo -e "\n1\n2\n3\n4\n")
IFS=$'\n';
for r in "$hh"; do echo $r; done
1 2 3 4
echo -e "$hh"
1
2
3
4
Use a while loop:
$ while read -r r; do echo $r; done <<< "$hh"
1
2
3
4
The correct answer is, you don't read line-by-line with a for loop. Use a while loop instead with the read builtin:
while IFS= read -r line; do
echo "$line"
done <<< "$hh"
Though using while read is definitely can solve this but if you really want to use for loop then you need to use IFS=$'\n' to read your input string in the bash's for loop:
hh=$(echo -e "\nname n1\nval n2\n3\nfoo n4\n")
IFS=$'\n' && for r in $hh; do echo "r='$r'"; done
r='name n1'
r='val n2'
r='3'
r='foo n4'
Remove the quotes from around $hh and the original code works fine:
hh=$(echo -e "\n1\n2\n3\n4\n")
IFS=$'\n'
for r in $hh; do echo "Value: $r"; done
# output:
Value: 1
Value: 2
Value: 3
Value: 4
It's only the expansion of the $hh variable in the for loop that DOES NOT need quoting.
This code works.
hh=$(echo -e "\n1\n2\n3\n4\n")
echo "Starting string"
echo -e "$hh"
IFS=$'\n';
echo "Original Code"
for r in "$hh"; do echo r is $r; done
echo "Fixed Code 1"
IFS=$'\n';
for r in "$hh"; do echo r is "$r"; done
echo "Fixed Code 2"
IFS=$'\n';
for r in $hh ; do echo r is $r ; done
And produces,
Starting string
1
2
3
4
Original Code
r is 1 2 3 4
Fixed Code 1
r is
1
2
3
4
Fixed Code 2
r is 1
r is 2
r is 3
r is 4

Looping file contents in bash

I have a file /tmp/a.txt whose contents I want to read in a variable many number of times. If the EOF is reached then it should start from the beginning.
i.e. If the contents of the file is "abc" and I want to get 10 chars, it should be "abcabcabca".
For this I wrote an obvious script:
while [ 1 ];
do cat /tmp/a.txt;
done |
for i in {1..3};
do read -N 10 A;
echo "For $i: $A";
done
The only problem is that it hangs! I have no idea why it does so!
I am also open to other solutions in bash.
To repeat over and over a line you can :
yes "abc" | for i in {1..3}; do read -N 10 A; echo "for $i: $A"; done
yes will output 'forever', but then the for i in 1..3 will only execute the "do ... done;" part 3 times
yes add a "\n" after the string. If you don't want it, do:
yes "abc" | tr -d '\n' | for i in {1..3}; do read -N 10 A; echo "for $i: $A"; done
In all the above, note that as the read is after a pipe, in bash it will be in a subshell, so "$A" will only available in the "do....done;" area, and be lost after!
To loop and read from a file, and also not do that in a subshell:
for i in {1..3}; do read -N 10 A ; echo "for $i: $A"; done <$(cat /the/file)
To be sure there is enough data in /the/file, repeat at will:
for i in {1..3}; do read -N 10 A ; echo "for $i: $A"; done <$(cat /the/file /the/file /the/file)
To test the latest: echo -n "abc" > /the/file (-n, so there is no trainling newline)
The script hangs because of the first loop. After the three iterations of the second loop (for) are done, the first loop repeatedly starts new cat instances which read the file and then write the content abc to the pipe. The write to the pipe doesn't work any more in the later iterations. Yes, there is a SIGPIPE kill, but to the cat command and not to the loop itself. So the solution is to catch the error in the right place:
while [ 1 ];
do cat /tmp/a.txt || break
done |
for i in {1..3};
do read -N 10 A;
echo "For $i: $A";
done
Besides: output is following:
For 1: abcabcabca
For 2: bcabcabcab
For 3: cabcabcabc
<-- (Here the shell hangs no more)

bash script, erase previous line?

In lots of Linux programs, like curl, wget, and anything with a progress meter, they have the bottom line constantly update, every certain amount of time. How do I do that in a bash script? All I can do now is just echo a new line, and that's not what I want because it builds up. I did come across something that mentioned "tput cup 0 0", but I tried it and it's kind of quirky. What's the best way?
{
for pc in $(seq 1 100); do
echo -ne "$pc%\033[0K\r"
usleep 100000
done
echo
}
The "\033[0K" will delete to the end of the line - in case your progress line gets shorter at some point, although this may not be necessary for your purposes.
The "\r" will move the cursor to the beginning of the current line
The -n on echo will prevent the cursor advancing to the next line
You can also use tput cuu1;tput el (or printf '\e[A\e[K') to move the cursor up one line and erase the line:
for i in {1..100};do echo $i;sleep 1;tput cuu1;tput el;done
Small variation on linuts' code sample to move the cursor not to the beginning, but the end of the current line.
{
for pc in {1..100}; do
#echo -ne "$pc%\033[0K\r"
echo -ne "\r\033[0K${pc}%"
sleep 1
done
echo
}
printf '\r', usually. There's no reason for cursor addressing in this case.
To actually erase previous lines, not just the current line, you can use the following bash functions:
# Clears the entire current line regardless of terminal size.
# See the magic by running:
# { sleep 1; clear_this_line ; }&
clear_this_line(){
printf '\r'
cols="$(tput cols)"
for i in $(seq "$cols"); do
printf ' '
done
printf '\r'
}
# Erases the amount of lines specified.
# Usage: erase_lines [AMOUNT]
# See the magic by running:
# { sleep 1; erase_lines 2; }&
erase_lines(){
# Default line count to 1.
test -z "$1" && lines="1" || lines="$1"
# This is what we use to move the cursor to previous lines.
UP='\033[1A'
# Exit if erase count is zero.
[ "$lines" = 0 ] && return
# Erase.
if [ "$lines" = 1 ]; then
clear_this_line
else
lines=$((lines-1))
clear_this_line
for i in $(seq "$lines"); do
printf "$UP"
clear_this_line
done
fi
}
Now, simply call erase_lines 5 for example to clear the last 5 lines in the terminal.
man terminfo(5) and look at the "cap-nam" column.
clr_eol=$(tput el)
while true
do
printf "${clr_eol}your message here\r"
sleep 60
done

Bourne Shell For i in (seq)

I want to write a loop in Bourne shell which iterates a specific set of numbers. Normally I would use seq:
for i in `seq 1 10 15 20`
#do stuff
loop
But seemingly on this Solaris box seq does not exist. Can anyone help by providing another solution to iterating a list of numbers?
try
for i in 1 10 15 20
do
echo "do something with $i"
done
else if you have recent Solaris, there is bash 3 at least. for example this give range from 1 to 10 and 15 to 20
for i in {1..10} {15..20}
do
echo "$i"
done
OR use tool like nawk
for i in `nawk 'BEGIN{ for(i=1;i<=10;i++) print i}'`
do
echo $i
done
OR even the while loop
while [ "$s" -lt 10 ]; do s=`echo $s+1|bc`; echo $s; done
You can emulate seq with dc:
For instance:
seq 0 5 120
is rewritten as:
dc -e '0 5 120 1+stsisb[pli+dlt>a]salblax'
Another variation using bc:
for i in $(echo "for (i=0;i<=3;i++) i"|bc); do echo "$i"; done
For the Bourne shell, you'll probably have to use backticks, but avoid them if you can:
for i in `echo "for (i=0;i<=3;i++) i"|bc`; do echo "$i"; done
#!/bin/sh
for i in $(seq 1 10); do
echo $i
done
I find that this works, albeit ugly as sin:
for i in `echo X \n Y \n Z ` ...
for i in `seq 1 5 20`; do echo $i; done
Result:
5
10
15
20
$ man seq
SEQ(1) User Commands SEQ(1)
NAME
seq - print a sequence of numbers
SYNOPSIS
seq [OPTION]... LAST
seq [OPTION]... FIRST LAST
seq [OPTION]... FIRST INCREMENT LAST

Resources