Infinite 'for' loop with Bash - bash

I have a script like
#!/bin/bash
for i in {1..xx};do break="$i"
If....; then Some command
else break;fi
done
I need something which can repeat this script n times with incrementing $i.
I tried this:
For (( ; ; )); do i=1 && echo $i && ((i++));done
But this always shows 1, not an incrementing number. I also tried $((i+=1)).
Where xx is must be endless number.
Where break="$i" gives me how many times repeated script.

Using for to create an endless loop is unidiomatic, but not hard. Just make the ending condition never true; or, trivially, omit it.
for((i=0; ;++i)); do
echo "$i"
done
The above is Bash only. The usual solution, which works in POSIX sh too, is to use while true (but then that doesn't come with an incrementing index, if that's really what you need).

Related

How to delete folders that fail a condition in bash script

I have a number of folders that are constantly and automatically generated. Some are garbage and need to be cleared out. Each folder produces a generations.txt which I want to count the important lines to determine whether or not the folder should be deleted. I'd like to have a bash script I can run every so often to clean things up.
Here's what I have. I can echo the command I want but I don't believe it outputs the integer to compare to 5. Any suggestions would really help me out. Please and thank you!
#!/bin/bash
SEARCHABLES="grep -Evc 'Value:' "
for d in */
do
PATH=$d'generations.txt'
COMMAND=$SEARCHABLES$PATH
if $COMMAND < 5
then
rm -rf $d
fi
done
You're not getting the output of the command, you need $(...) to execute a command and substitute its output.
To perform the arithmetic comparison, you have to put it inside ((...)).
#!/bin/bash
SEARCHABLES="grep -Evc 'Value:' "
for d in */
do
PATH="$d"'generations.txt'
COMMAND=$SEARCHABLES$PATH
if (( $($COMMAND) < 5 ))
then
rm -rf "$d"
fi
done
See BashFAQ/050 - I'm trying to put a command in a variable, but the complex cases always fail!
for a more detailed explanation.
In short, embedding a command in a variable is a faulty approach to the problem here because the single quotes in 'Value:' will be treated like literal data to search for. Syntax parsing happens before expansions, so you can't embed quotes in a variable like that. What you need is a function:
_count() {
grep -Evc 'Value:' "$1"
}
_count "$PATH"
Then compare the output of the function using an arithmetic expression:
occurrences=$( _count "$PATH" )
if (( occurrences < 5 )) ; then
...
fi

Bash variable math expansion not working with printf

I'm trying to get a formatted number that increments every time through a while loop.
I've got fnum=$(printf "%03d" $((++num)) ) but the number doesn't increment. fnum is "000" and remains at that.
Of course num=$((++num)) ; fnum=$(printf "%03d" $num) works but I'm wondering why the first one doesn't increment the number.
You don't need comamnd-substitution($(..)) in the first place to store the output of printf use the -v option to store it in a variable
printf -v fnum "%03d" $((++num))
Also the num variable is updated in a sub-shell, $(..) runs the command inside in a separate shell. The value of num incremented will never be reflected back in the parent shell.
With:
$(printf "%03d" $((++num)))
the command inside $() is run in a sub-shell so changes to the num variable in there are not carried back to the parent shell.
With the working version, num=$((++num)) is executed in the context of the current shell, so num is modified.
Of course, it makes little sense to assign back to num since the side-effect of ++ is changing num anyway, so you can just do something like:
((++num)) ; fnum=$(printf "%03d" $num)
And you can totally avoid starting a sub-shell and just use internal bash stuff, which will make a large difference if you need to do this a lot(a):
((++num)) ; fnum=000${num} ; fnum=${fnum: -3} ; doSomethingWith ${fnum}
(a) As seen in the following script:
rm -f qq[12]
time (
var=0
while [[ ${var} -lt 99999 ]] ; do
((++var))
svar=$(printf "%05d" ${var})
echo ${svar}
done
) >>qq1
time (
var=0
while [[ ${var} -lt 99999 ]] ; do
((++var))
svar=00000${var}
svar=${svar: -5}
echo ${svar}
done
) >>qq2
The first snippet takes a little over nine seconds CPU time (user+system) to run, the second completes in about a second (the difference is even more pronounced if you measure wall clock time, since many copies of "printf in a subshell" need to be started):
real 0m30.875s
user 0m0.320s
sys 0m9.144s
real 0m1.008s
user 0m0.924s
sys 0m0.080s

This simple bash script doesn't work. What am I doing wrong?

I'm trying to create the script that will simply do cd ../ times n, where n is whatever I pass to it, or 1 by default:
STEPS=${1:-1}
for i in $STEPS; do
cd ..
done
It doesn't give me any errors and it does nothing..
You should be able to source it to do what you wish, e.g.
. yourscript.sh 3
to change directory 3 times. (notice the dot before the yourscript.sh)
After you fix the script at least, e.g.
#!/bin/bash
STEPS=$1
for ((i=1; i<=$STEPS; i++)); do
cd ..
done
Thanks #Charles Duffy for mentioning sourcing, and thanks #chepner for the fixed for loop.
Some info:
Shell scripts are run inside a subshell, and each subshell has its own
concept of what the current directory is. The cd succeeds, but as soon
as the subshell exits, you're back in the interactive shell and
nothing ever changed there.
from here
In bash, you generally don't want to generate a sequence of numbers to iterate over. Use the C-style for loop:
for ((i=1; i<=$STEPS; i++)); do
cd ..
done
If this is in a file, you need to source it ( . ./cd-up) rather than executing it (sh ./cd-up or ./cd-up, etc).
If you are, in fact, using zsh, you can simply use the repeat construct:
repeat $STEPS do cd ..; done
or its shorter form for simple commands
repeat $STEPS cd ..
Assuming that $STEPS is always a single number, then your for loop will only run for one iteration. This is because the type of loop that you're using expects a list of words:
for name in word1 word2 word3; do
# some stuff
done
This will run three times, assigning each value in the list to variable $name.
Using a C-style loop, you could do this:
steps=${1:-1}
for (( i = 0; i < steps; ++i )); do
cd ..
done
Alternatively, in any POSIX shell you can use a while loop:
steps=${1:-1}
while [ $steps -gt 0 ]; do
cd ..
steps=$(( steps - 1 ))
done
If you pass in something that's not an integer, then neither of these approaches will work!
Remember that if you run this as a script, it will change the directory while the script is running but the parent shell (the one you ran the script from) will be unaffected.

For loop with an argument based range

I want to run certain actions on a group of lexicographically named files (01-09 before 10). I have to use a rather old version of FreeBSD (7.3), so I can't use yummies like echo {01..30} or seq -w 1 30.
The only working solution I found is printf "%02d " {1..30}. However, I can't figure out why can't I use $1 and $2 instead of 1 and 30. When I run my script (bash ~/myscript.sh 1 30) printf says {1..30}: invalid number
AFAIK, variables in bash are typeless, so how can't printf accept an integer argument as an integer?
Bash supports C-style for loops:
s=1
e=30
for i in ((i=s; i<e; i++)); do printf "%02d " "$i"; done
The syntax you attempted doesn't work because brace expansion happens before parameter expansion, so when the shell tries to expand {$1..$2}, it's still literally {$1..$2}, not {1..30}.
The answer given by #Kent works because eval goes back to the beginning of the parsing process. I tend to suggest avoiding making habitual use of it, as eval can introduce hard-to-recognize bugs -- if your command were whitelisted to be run by sudo and $1 were, say, '$(rm -rf /; echo 1)', the C-style-for-loop example would safely fail, and the eval example... not so much.
Granted, 95% of the scripts you write may not be accessible to folks executing privilege escalation attacks, but the remaining 5% can really ruin one's day; following good practices 100% of the time avoids being in sloppy habits.
Thus, if one really wants to pass a range of numbers to a single command, the safe thing is to collect them in an array:
a=( )
for i in ((i=s; i<e; i++)); do a+=( "$i" ); done
printf "%02d " "${a[#]}"
I guess you are looking for this trick:
#!/bin/bash
s=1
e=30
printf "%02d " $(eval echo {$s..$e})
Ok, I finally got it!
#!/bin/bash
#BSD-only iteration method
#for day in `jot $1 $2`
for ((day=$1; day<$2; day++))
do
echo $(printf %02d $day)
done
I initially wanted to use the cycle iterator as a "day" in file names, but now I see that in my exact case it's easier to iterate through normal numbers (1,2,3 etc.) and process them into lexicographical ones inside the loop. While using jot, remember that $1 is the numbers amount, and the $2 is the starting point.

BASH Arithmetic Issues

I'm working in BASH and I'm having an idiot moment right now. I've got a project I'm working on that I'm going to need to use some very basic arithmetic expressions and I just realized that a lot of my problems with it are because my variables are not updating. So I threw together a basic algorithm that increments a variable by another variable with a while loop until a certain number is reached.
counter=1
counter2=0
while [[ counter2 < 10 ]]; do
counter2=$(($counter2+$counter))
echo $counter
echo $counter2
done
I run the script. Does nothing. I set the < to > just for kicks and an infinite loop occurs with a repeated output of:
1
0
1
0
Forever and ever until I stop it. So it's obvious the variables are not changing. Why? I feel like such an idiot because it must be something stupid I'm overlooking. And why, when I have <, it also isn't an infinite loop? Why doesn't it print anything at all for that matter? If counter2 is always less than 10, why doesn't it just keep going on forever?
Thanks folks in advance.
EDIT: Well, I realize why it wasn't outputting anything for when the check is <... I should have been using $counter2 instead of just counter2 to get the actual value of counter2. But now it just outputs:
1
2
And that's it... I feel like such a derp.
If this is all bash (100% sure) then you could use declare -i in order to explicitly set type of your variables and then your code will be as simple as :
declare -i counter=1
declare -i counter2=0
while [[ $counter2 -lt 10 ]]; do
counter2=$counter2+$counter
echo $counter
echo $counter2
done
EDIT:
In bash, you can do arithmatic comparison with double paranethesis. So, your while can be written as:
while (($counter2 < 10)) ; do
Inside the $((...)), don't use the sigil ($).
counter2=$((counter2+counter))
In bash, you can use c-like for loops:
for (( counter2=0; counter2<10; counter2+=counter ))
do
echo $counter": "$counter2
done
Often you will find this construct more appealing to use:
for counter2 in {0..9}
do
echo $counter": "$counter2
done

Resources