Compare a time command result as a number in IF - bash

My.sh measures execution time in seconds:
/usr/bin/time -f '%e' long-command
I need to compare that time, that should be stored in $exptime, to some number, like:
if (( $exptime < 80 )) ; then
echo "OK" ; exit 0
elif (( $exptime > 80 )) ; then
echo "NOK" ; exit 1
else echo "error"
fi
I was trying to assign the output of a command to a variable using command substitution exptime=$(), and I get the result on console, but it doesn't work to compare that number in this script (0/1 of the script is used further). But, instead of just using the number in IF, script is called 2 more times.
There are many questions on How to set a variable to the output of a command in Bash? but I couldn't figure out the solution.

I resolved this by avoiding time and using just long-command followed by exptime=$SECONDS.
Script is iterative and SECONDS starts from 0 each time.
Not sure why time works with simple case of exptime=$( { time sleep 3 ; } 2>&1 ), where it's used as number, and not with my long-command, where echo shows it's a correct number but later in IF it repeats command with division by 0 (error token is....

Related

Bash while loop till EOF

I need to write a program that calculates the arithmetic mean and variance with intiger division, but i don't now how many numbers will be entered. Example input:
5
4
1
5
2
6
Example output:
3
8
For now when i enter x instead of a number, the loop ends, but those numbers are read from file, so i think it should be something like:
while read -r num; do
if [[ "$num" -eq EOF ]]; then #that condition is my question, what should be inside [[]]?
break
fi
else
#do sth
done
#the rest of the program
You don't get a special value when the end of the input file is reached; rather, read exits with a non-zero exit status, which terminates the loop. For example:
count=0
total=0
while read -r num; do
count=$((count + 1))
total=$((total + num))
done
avg=$((total / count))

Check if every argument is an integer in shell

I've been killing myself over this trying to figure it out, and I know it's probably super simple, so hoping a new pair of eyes can help. I have a Bourne shell (sh) script that I'm writing and it takes a list of integers as the input (and amount from 1 integer up, ideally I'd like to take both positive and negative ints). I'm trying to do an error check for the case if someone inputs something other than an integer. They could input "1 2 3 4 5 a" and it'd give an error, because the a is not an int.
I have an error check for no inputs that works, and I have code that does stuff to the list of integers themselves, but even when strings are given it still gets to my final code block.
I currently have a for loop to iterate through each item in the list of integers, then an if loop to give the error message if the argument in question isn't an int. I've tried a few different versions of this, but this is the most recent one so I've put it below.
for i in $#; do
if [ $i -ge 0 ] 2>/dev/null; then
echo "Invalid input: integers only."
exit 1
fi
done
#!/bin/sh
#
for i in "$#"
do
case "${i#[-+]}" in
0)
echo cannot be 0?
exit 1
;;
*[!0-9]* | '')
echo not int
exit 2
;;
esac
done
echo i\'m ok
This should work, for both positive and negative ints. And if you admit that 0 is an integer, just delete the first case.
Almost duplicate: BASH: Test whether string is valid as an integer?
And here is a good answer for posix. https://stackoverflow.com/a/18620446/7714132
You could use a regex:
my_script.sh
for i in $# ; do
if ! [[ "$i" =~ ^-?[0-9]+$ ]] ; then
echo "Invalid input: integers only."
exit 1
fi
done
Example:
$ sh my_script.sh 1 2 3 4
$ sh my_script.sh 1 2 -12
$ sh my_script.sh 1 2 -12-2
Invalid input: integers only.
$ sh my_script.sh 1 2 a b
Invalid input: integers only.
Explanation of the regex:
^: beginning of the string
-?: 0 or 1 times the character -
[0-9]+: 1 or more digit
$: end of the string
In POSIX sh, you can match your string against a glob with case:
#!/bin/sh
for i
do
case "$i" in
*[!0-9]*)
echo "Integers only" >&2
exit 1
esac
done
echo "Ok"
Here's how it runs:
$ ./myscript 1 2 3 4 5
Ok
$ ./myscript 1 2 3 4 5 a
Integers only
The problem with your approach is primarily that you're checking for success rather than failure: [ will fail when the input is invalid.

How to influence a bash while loop with a timed global variable?

I have a while loop in a bash script which should do something different at the beginning and at every 5 second interval. Any previous loop is allowed to complete. The 5s interval is indicated by the do_different global variable set by the heartbeat function. An additional complication is that a normal while loop completes in an unknown amount of time (simplified with RANDOM in below script).
Using cron is not an option, neither is timing the random process.
I already unsuccessfully tried using a pipe as well as process substitution.
The whole script may be re-factored.
#!/bin/bash
function heartbeat {
do_different=true
while sleep 5s
do
do_different=true
done
}
heartbeat &
while true
do
if $do_different
then
echo 'Something different'
do_different=false
i=0
else
# process of random duration; not important
r=$(( 1 + RANDOM % 3 ))
sleep "${r}s"
i=$((i + r))
echo "$i"
fi
done
The problem is that while loops are executed in a new subshell with their own copy of the global variables.
There are many possible workarounds. Grouping all commands into the subshell is what worked here for me.
#!/bin/bash
t_lastdue=$(date --date='5seconds ago' +%s)
while true
do
t_now=$(date +%s)
t_elapsed=$((t_now - t_lastdue))
if [ $t_elapsed -ge 5 ]
then
echo 'Something different'
t_lastdue=$((t_lastdue + 5))
i=0
else
# process of random duration; not important
r=$(( 1 + RANDOM % 3 ))
sleep "${r}s"
i=$((i + r))
echo "$i"
fi
done

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}

Global variable is reset if loop send output to pipe

According the bash(1) man pages, when I run the following:
set -e
x=2
echo Start $x
while [ $((x--)) -gt 0 ]; do echo Loop $x; done | cat
echo End $x
The output will be:
Start 2
Loop 1
Loop 0
End 2
After the loop (runs as a subshell) the variable x reset to 2. But if I remove the pipe the x will be updated:
Start 2
Loop 1
Loop 0
End -1
I need to change the x but, I need the pipe too.
Any idea how to get around this problem?
bash always (at least as of 4.2) runs all non-rightmost parts of a pipeline in a subshell. If the value of x needs to change in the calling shell, you must rewrite your code to avoid the pipeline.
One horrible-looking example:
# If you commit to one bash feature, may as well commit to them all:
# Arithmetic compound: (( x-- > 0 ))
# Process substitution: > >( cat )
while (( x-- > 0 )); do echo Loop $x; done > >( cat )

Resources