Bash: Capture newline with `read` - bash

I have a loop that I want to break on any user input.
Contrived example – a timer that counts down from 10:
counter=10
while (( counter > 0 )) && [[ -z $input ]]; do
printf '\rRestart in %s seconds' "$counter"
read -n 1 -t 1 input
(( counter-- ))
done
echo "Completed"
This works appropriately for all user input except for Enter, and I fully expect that Enter will be the most common input.
How can I capture Enter using Bash's read command?

The bash manual says that read returns success (return code zero) unless it times out. So how about this:
for (( counter=10 ; counter > 0 ; counter-- )); do
printf "\rRestart in %s seconds " $counter
read -n 1 -t 1 input && break
done
echo "Completed"

Tell read to use NUL as its delimiter:
IFS='' read -r -d '' -n 1 -t 1 input
So, in full context:
counter=10; input=''
while (( counter > 0 )) && [[ -z $input ]]; do
printf "\rRestart in %s seconds" "$counter"
IFS='' read -n 1 -t 1 -d '' input
(( counter-- ))
done

Related

Bash print the number of incorrect file line

A beginner asking for help (:
So, I have a script that checks brackets in a text file and tells whether they are closed correctly. However, I also want to make my script print out the number of the incorrect line (where brackets are closed incorrectly). I have tried counting file lines and then making a nested while loop, however, it doesn't work for me at all ): Are there any simple solutions for this? I would like to leave the LINE counter if that's possible o:
INPUT="$1"
count=0
LINE=0
# Check if file exists
[ ! -f $INPUT ] && { echo "file $INPUT do not exist."; exit ; }
# Count file lines and read every char
while IFS= read -r LINE
do
LINE=$(( LINE + 1 ))
while read -n1 char
do
[ "$char" == "(" ] && (( count++ ))
[ "$char" == ")" ] && (( count-- ))
if [ "$count" -lt 0 ]
then
break
fi
done
done < "$INPUT"
if [ "$count" -lt 0 ]
then
echo "Found a mistake in $LINE line "
else
echo "Everything's correct"
fi
You have a couple of problems:
Your read in the inner loop consumes the input from the file, not from LINE.
The line
LINE=$(( LINE + 1 ))
is really wrong: LINE is the content of the line of your file, and your trying to add 1 to it. Weird.
Your break only breaks the inner loop (it should break two loops). Use break 2 for this.
Here's a working version of your script:
input=$1
count=0
linenb=0
# Check if file exists
[[ -f $input ]] || { echo "Error: file $input do not exist."; exit 1; }
# Count file lines and read every char
while IFS= read -r line; do
((++linenb))
while read -n1 char; do
[[ $char == '(' ]] && ((++count))
[[ $char == ')' ]] && ((--count))
((count>=0)) || break 2
done <<< "$line"
done < "$input"
if ((count<0)); then
echo "Found a mistake in line #$linenb:"
printf '%s\n' "$line"
else
echo "Everything's correct"
fi
Note that I used more ((...)) and [[...]].
I also used lowercase variable names, as your computer isn't deaf: you don't need to shout the name of the variable. (And it's nicer to the eye). And it's good practice to use lowercase variable names, as there's no chance that they clash with Bash's own variables.

sign automatic number to my file in bash

I am trying to assign a unique number to my strings that are redirected and stored in a file.
You have to fill in a form, and I want to send a unique number with it.
example:
echo fill in the form
echo place
read place
date
"place: $place, time $(date) >> List
It has to look something like this.
outcome in List
number 1, place, time
number 2, place, time
number 3, place, time
I used a loop but I got the following outcome.
number 0, place, time
number 0, place, time.
I think I need a function that checks the last number given in the file and add 1 to it, but I wonder if there is an easier way.
Perhaps this one:
#!/bin/bash
# Optionally truncate file
# : > List
I=0
while
read -p "Place: " PLACE
read -p "Time: " TIME
echo "number $((++I)), $PLACE, $TIME" >> List
read -n 1 -p "Continue? " && [[ $REPLY == [yY] ]]
do
continue
done
Update:
#!/bin/bash
# Optionally truncate file
# : > List
shopt -s extglob
for (( I = 1;; ++I )); do
for (( ;; )); do
read -p "Place: " PLACE
read -p "Time: " TIME
until
read -p "Save data? "
[[ $REPLY == [nN]?([oO]) ]]
do
[[ $REPLY == [yY]?([eE][sS]) ]] && break 2
echo "Please answer Y[es] or N[o]."
done
done
echo "Saving \"number $I, $PLACE, $TIME\"."
echo "number $I, $PLACE, $TIME" >> List
until
read -p "Continue? "
[[ $REPLY == [yY]?([eE][sS]) ]]
do
[[ $REPLY == [nN]?([oO]) ]] && break 2
echo "Please answer Y[es] or N[o]."
done
echo
done
To generate a 16-char random hex string, you can use r=$(openssl rand -hex 8)
To find the last number used and increment it, you can do
prev=$(awk -F, 'END {print $1}' List)
printf "%d, place:%s, time:%s\n" $((prev+1)) "$place" "$(date)" >> List
This is subject to a race condition if the script can be executed simultaneously
To start at 1:
if [[ ! -f List ]]; then
prev=1
else
prev=$(awk -F, 'END {print $1}' List)
fi
printf "%d, place:%s, time:%s\n" $((prev+1)) "$place" "$(date)" >> List

Bash incrementation breaking scripts

I'm having a weird issue incrementing a bash variable that seems
to be breaking after my first attempt at incremntation that I cannot
pin down, here is a sample of what I am doing and the debug output,
anyone see any reason this should NOT work?
I am currently on GNU bash, version 4.2.45(1)-release (i686-pc-linux-gnu)
#!/bin/bash
set -ex
declare -i elem=0
echo $elem # 0
(( elem++ )) # breaks
echo $elem # 1 but never encountered
while IFS=$'\n' read -r line || [[ -n "$line" ]]; do
(( elem++ ))
echo $elem
done <"${1}" # foo\nbar\nbaz
Output
./incr.sh test
+ declare -i elem=0
+ echo 0
0
+ (( elem++ ))
The weirdest part is by changing the initial incrementor to (( elem+=1 ))
the entire program increments correctly, this seems extremely buggy to the eye...
#!/bin/bash
set -ex
declare -i elem=0
echo $elem
(( elem+=1 ))
echo $elem
while IFS=$'\n' read -r line || [[ -n "$line" ]]; do
(( elem++ ))
echo $elem
done <"${1}" # foo\nbar\nbaz
Output
+ declare -i elem=0
+ echo 0
0
+ (( elem+=1 ))
+ echo 1
1
+ IFS='
'
+ read -r line
+ (( elem++ ))
+ echo 2
2
+ IFS='
'
+ read -r line
+ (( elem++ ))
+ echo 3
3
+ IFS='
'
+ read -r line
+ (( elem++ ))
+ echo 4
4
+ IFS='
'
+ read -r line
+ [[ -n '' ]]
set -e makes your script exit when any command returns failure.
(( 0 )), and equivalently elem=0; (( elem++ )) returns failure.
Therefore, the script exits.
If you set -e and want to run commands whose status you don't care, about, you can use
(( elem++ )) || true

how to extract numbers from this echo into separate variables?

Sorry about bits and snippit of information
So I am writing an average shell script program
so if use inputs
echo 1 3, .... | sh get_number
I would have to pull the numbers seperated by spaces from echo to be
var1 = 1, var2= 3, etc.
I tried
#!/bin/sh
sum=0
for i in $*
do
sum=`expr $sum + $i`
done
avg=`expr $sum / $n`
echo Average=$avg
but doesnt work....
do I include a read here?
also how would I do
sh get_number <file1>, <file2>... to grab numbers in them and sum them
in shell script?
Thanks
Sounds like you are looking for the read shell builtin:
% echo "1 2 3 4" | read a b stuff
% echo $b
2
% echo $stuff
3 4
To fix up your code:
for i in $*; do
sum=$(( sum + i ))
n=$(( n + 1 ))
done
echo "Average=$(( sum / n ))"
#!/bin/sh
while [ $# -gt 0 ]; do
(( i++ ))
(( sum += $1 ))
shift
done
echo "Average=$(( sum/i ))"
Note: This fails in dash which is the closest shell I could find to a real sh.
An example of reading values from files passed as command line arguments or from lines read from stdin:
add_to_sum() {
set $*
while [ $# -gt 0 ]; do
I=`expr $I + 1`
SUM=`expr $SUM + $1`
shift
done
}
I=0
SUM=0
if [ $# -gt 0 ]; then
# process any arguments on the command line
while [ $# -gt 0 ]; do
FILE=$1
shift
while read LINE; do
add_to_sum "$LINE"
done < "$FILE"
done
else
# if no arguments on the command line, read from stdin
while read LINE; do
add_to_sum "$LINE"
done
fi
# be sure not to divide by zero
[ $I -gt 0 ] && echo Average=`expr $SUM / $I`

Bash - working with ASCII so slow

I have a bash script that takes 2 arguments - a character and integer. I want to print chars in aplhabet starting with that character of length of that integer (modulo the alphabet)
#!/bin/bash
[[ $# != 2 ]] && echo Exactly 2 argument needed && exit 1
[[ "$1" =~ ^[a-zA-Z]$ ]] || { echo Enter only one char; exit 2; }
[[ "$2" =~ ^[0-9]+$ ]] || { echo Enter integer; exit 3; }
letter="$1"
cnt="$2"
letter=`printf "%d" \'$letter`;
z=`printf "%d" \'z`
a=`printf "%d" \'a`
[[ "$1" =~ ^[[:upper:]]$ ]] && { ((letter+=32)); } # make lower
while [[ "$cnt" -gt 0 ]]; do
printf \\$(printf "%03o" "$letter")
((letter++))
[[ "$letter" -gt $z ]] && letter=$a # alphabet modulo
((cnt--))
done
echo ""
This script works perfectly fine, but it's too slow. I have to call 2x n (value of int) the printf program.
My question is if there is something like enable printf working solution to boost up the speed of printf execution or some solution not using printf at all.
I need to use bash and I just want to know how to handle ASCII the fastest possible way. I'm scripting on cygwin atm, so there could be some speed fails related to it.
This script is just an illustration of problem, I don't want some "increase stdout flush buffer size" solutions. Thanks :)
Here you go:
#!/bin/bash
[[ $# != 2 ]] && echo Exactly 2 argument needed && exit 1
[[ "$1" =~ ^[a-zA-Z]$ ]] || { echo Enter only one char; exit 2; }
[[ "$2" =~ ^[0-9]+$ ]] || { echo Enter integer; exit 3; }
alpha=`echo {a..z}|sed 's/ //g'`
letter="$1"
cnt="$2"
letter=$(expr `printf "%d - %d + 1" \'$letter \'a`;)
[[ "$1" =~ ^[[:upper:]]$ ]] && { ((letter+=32)); } # make lower
echo -n `echo $alpha |tail -c +$letter |head -c $cnt`
((cnt=cnt-26))
while [[ "$cnt" -gt 0 ]]; do
echo -n `echo $alpha |head -c $cnt`
((cnt=cnt-26))
done
echo ""
This program uses brace expansion to expand a..z into the list of characters and then displays this string cut to size using head and tail. It could be even more optimized, but that is an exercise for you.
Performance comparison(<1> is code in question, <2> is code in this answer):
* Startup section has 4 commands in <2> as compared to 3 in <1>.
* <2> runs 4 commands (echo (x2), head, tail) per 26 characters, as compared to 1 command per character in <1>.
* <2> has one arithmetic operation per 26 characters while <1> has 2 arithmetic operations per character.
samveen#precise:/tmp$ time bash 1.sh a 1000
abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl
real 0m2.065s
user 0m0.244s
sys 0m0.552s
samveen#precise:/tmp$ time bash 2.sh a 1000
abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl
real 0m0.285s
user 0m0.024s
sys 0m0.060s
Further reading: man bash

Resources