I'm running an infinite while loop in bash that has a counting element to it.
while :
do
#stuff including counting
trap break int
done
This works as I would like for the most part. When I hit Ctrl+C, the loop stops, but the script continues. However, the loop stops in the middle of the loop which means that the final count is inaccurate.
Is there a way to make this loop break at the very end?
Instead of executing break you can set a flag and break the loop at a designated location when the flag is set. Note that in a loop the loop head can also be seen as the very end of the loop.
state=run
trap 'state=end' int
while [ "$state" = run ]
do
# stuff
done
If you want to break somewhere in the middle use
state=run
trap 'state=end' int
while :
do
# stuff
[ "$state" = end ] && break
# more stuff
done
You can test the behavior quiet nicely by replacing # stuff with for i in {1..20}; do printf .; sleep 0.1; done; echo:
As we can see in the clip above, the outer loop finishes its current iteration (# stuff) until the very end.
Related
loopcount=1
loopmax=5
while [ $loopcount -le $loopmax ]
do
echo "loop iteration :$loopcount"
((loopcount=loopcount+1))
done
for this i am getting o/p like this
loop iteration :1
loop iteration :2
loop iteration :3
loop iteration :4
loop iteration :5
but if i change program ((loopcount=loopcount+1)) to (loopcount=loopcount+1) i am getting bellow output.
loop iteration :1
loop iteration :1
loop iteration :1
loop iteration :1
loop iteration :1
loop iteration :1
getting infinite times . what is difference of () and (()) ?
(...) means to run the given command in a subshell. ((...)) means to do the arithmetic operation within the parens.
Note that a subshell cannot change the variables of the parent shell, so in your example you never update the value of loopcount in the parent. Also, in your single paren example, you would not be doing arithmetic, you'd be assigning the string loopcount+1 to the variable loopcount so that if you did printf "%s\n" "$loopcount" after that you would get the output loopcount+1
From man bash:
(list) list is executed in a subshell environment (see COMMAND EXECUTION ENVIRONMENT below). Variable assignments and builtin commands that affect the shell's environment do not remain in effect after the command completes. The return status is the exit status of list.
[...]
((expression)) The expression is evaluated according to the rules described below under ARITHMETIC EVALUATION. If the value of the expression is non-zero, the return status is 0; otherwise the return status is 1. This is exactly equivalent to let "expression".
I'm currently learning Linux and as an homework, we have to create a few basic shell scripts. Nothing especially complicated but this one is giving me headaches. Here's my code :
until [ "$toPrint" == 'fin' ]
do
echo "Enter file name to print out :" ; read toPrint
sh ./afficher.sh "$toPrint"
done
Basically, I have another script called afficher.sh (I'm french so don't mind the french language used) and it reads whatever file name it gets as a parameter. However, the moment I type "fin", everything is supposed to stop except it still tries to print the file called "fin". I read a bit about the until loop on Internet and once it becomes True, it should stop, which is not my case...
Personally, I'd implement this like so -- with a while loop, not an until loop, and checking for the exit condition separately and explicitly:
while true; do
echo "Enter file name to print out :" ; read toPrint
[ "$toPrint" = fin ] && break
sh ./afficher.sh "$toPrint"
done
If you really want to use the loop's condition, you can do that:
while echo "Enter file name to print out :";
read toPrint &&
[ "$toPrint" != fin ]; do
sh ./afficher.sh "$toPrint"
done
...but personally, I'm less fond of this on aesthetic grounds.
You check the condition at the top of the loop, but you enter the value in the middle of the loop. The next thing you do after reading the value is always pass it to afficher.sh and then once that is done you check its value to see if you should stop. If you don't want to run afficher.sh on the fin value, you'll need to make sure your control flow allows you to do the comparison before you invoke afficher.sh.
I know how to check the status of the previously executed command using $?, and we can make that status using exit command. But for the loops in bash are always returning a status 0 and is there any way I can break the loop with some status.
#!/bin/bash
while true
do
if [ -f "/test" ] ; then
break ### Here I would like to exit with some status
fi
done
echo $? ## Here I want to check the status.
The status of the loop is the status of the last command that executes. You can use break to break out of the loop, but if the break is successful, then the status of the loop will be 0. However, you can use a subshell and exit instead of breaking. In other words:
for i in foo bar; do echo $i; false; break; done; echo $? # The loop succeeds
( for i in foo bar; do echo $i; false; exit; done ); echo $? # The loop fails
You could also put the loop in a function and return a value from it. eg:
in() { local c="$1"; shift; for i; do test "$i" = "$c" && return 0; done; return 1; }
Something like this?
while true; do
case $RANDOM in *0) exit 27 ;; esac
done
Or like this?
rc=0
for file in *; do
grep fnord "$file" || rc=$?
done
exit $rc
The real question is to decide whether the exit code of the loop should be success or failure if one iteration fails. There are scenarios where one make more sense than the other, and other where it's not at all clear cut.
The bash manual says:
while list-1; do list-2; done
until list-1; do list-2; done
[..]The exit status of the while and until commands is the exit status
of the last command executed in list-2, or zero if none was executed.[..]
The last command that is executed inside the loop is break. And the exit value of break is 0 (see: help break).
This is why your program keeps exiting with 0.
The break builtin for bash does allow you to accomplish what you are doing, just break with a negative value and the status returned by $? will be 1:
while true
do
if [ -f "./test" ] ; then
break -1
fi
done
echo $? ## You'll get 1 here..
Note, this is documented in the help for the break builtin:
help break
break: break [n] Exit for, while, or until loops.
Exit a FOR, WHILE or UNTIL loop. If N is specified, break N enclosing
loops.
Exit Status: The exit status is 0 unless N is not greater than or
equal to 1.
You can break out of n number of loops or send a negative value for breaking with a non zero return, ie, 1
I agree with #hagello as one option doing a sleep and changing the loop:
#!/bin/bash
timeout=120
waittime=0
sleepinterval=3
until [[ -f "./test" || ($waittime -eq $timeout) ]]
do
$(sleep $sleepinterval)
waittime=$((waittime + sleepinterval))
echo "waittime is $waittime"
done
if [ $waittime -lt $sleepinterval ]; then
echo "file already exists"
elif [ $waittime -lt $timeout ]; then
echo "waited between $((waittime-3)) and $waittime seconds for this to finish..."
else
echo "operation timed out..."
fi
I think what you should be asking is: How can I wait until a file or a directory (/test) gets created by another process?
What you are doing up to now is polling with full power. Your loop will allocate up to 100% of the processing power of one core. The keyword is "polling", which is ethically wrong by the standards of computer scientists.
There are two remedies:
insert a sleep statement in your loop; advantage: very simple; disadvantage: the delay will be an arbitrary trade-off between CPU load and responsiveness. ("Arbitrary" is ethically wrong, too).
use a notification mechanism like inotify (see: man inotify); advantage: no CPU load, great responsiveness, no delays, no arbitrary constants in your code; disadvantage: inotify is a kernel API – you need some code to access it: inotify-tools or some C/Perl/Python code. Have a look at inotify and bash!
I would like to submit an alternative solution which is simpler and I think more elegant:
(while true
do
if [ -f "test" ] ; then
break
fi
done
Of course of this is part of a script then you could user return 1 instead of exit 1
exit 1
)
echo "Exit status is: $?"
Git 2.27 (Q2 2020), offers a good illustration of the exit status in a loop, here within the context of aborting a failing test early (e.g. by exiting a loop), which is to say "return 1".
See commit 7cc112d (27 Mar 2020) by Junio C Hamano (gitster).
(Merged by Junio C Hamano -- gitster -- in commit b07c721, 28 Apr 2020)
t/README: suggest how to leave test early with failure
Helped-by: Jeff King
Over time, we added the support to our test framework to make it easy to leave a test early with failure, but it was not clearly documented in t/README to help developers writing new tests.
The documentation now includes:
Be careful when you loop
You may need to verify multiple things in a loop, but the following does not work correctly:
test_expect_success 'test three things' '
for i in one two three
do
test_something "$i"
done &&
test_something_else
'
Because the status of the loop itself is the exit status of the test_something in the last round, the loop does not fail when "test_something" for "one" or "two" fails.
This is not what you want.
Instead, you can break out of the loop immediately when you see a failure.
Because all test_expect_* snippets are executed inside a function, "return 1" can be used to fail the test immediately upon a failure:
test_expect_success 'test three things' '
for i in one two three
do
test_something "$i" || return 1
done &&
test_something_else
'
Note that we still &&-chain the loop to propagate failures from earlier commands.
Use artificial exit code 🙂
Before breaking the loop set a variable then check the variable as the status code of the loop, like this:
while true; do
if [ -f "/test" ] ; then
{broken=1 && break; };
fi
done
echo $broken #check the status with [[ -n $broken ]]
Hi I want to write and empty body loop. I just want the loop counter to increment so I want the cpu to stay busy without any IO operation. Here is what I have written but it gives me an error:
#!/bin/bash
for (( i = 0 ; i <= 1000000; i++ ))
do
done
root#ubuntu:~# ./forLoop
./forLoop: line 4: syntax error near unexpected token `done'
./forLoop: line 4: `done'
You must specify at least one command in a loop body.
The best command for such a purposes is a colon :, commonly used as a no-op shell command.
You could put a no op command inside the loop like true or false (do nothing successfully or unsuccessfully respectively).
This will be a tight loop and will burn CPU. Unless you want to warm up your computer on a cold morning, you can simply say i=1000000 and have the same effect as the loop.
What is it that you're trying to achieve?
#!/bin/bash
let i=0
while [[ $i -le 1000000 ]]; do
let i++
done
You could use sleep x if you want to delay for x number of seconds.
I'm trying to do something like these:
while[$read!="0"];
In this program
#!/bin/sh
i=0
cont=0
while[$read!="0"]; do
read number
cont=`expr $cont + $number`
i++
done
cont=`expr $cont / $i -1`
echo
I want to stop suming the entries when I give it a 0
tnx
The variable you're reading into is $number, so reference that rather than $read in your loop.
Whitespace is significant, so make sure to include spaces before, after, and between all of the items in your loop. (Confusingly, you must not include spaces in an assignment statement like i=0. i = 0 is wrong.)
For good measure, use double quotes around the variable. That's a good practice so that if the user hits enter without typing a number your script doesn't barf on the empty string.
while [ "$number" != "0" ]; do
Also, your i++ isn't right. There are various ways to write that, the simplest being:
let i++
In this, an infinite loop would be appropriate, since you know the condition on which you want to (hint) break out of the loop. The way to get an infinite loop in sh is: while true; do ...; done
Also, read has a -p option that lets you have a prompt (so you know what you're being asked to enter): read -p "Enter a number: " number