Can I remove the $ in bash C-style for loop? - bash

Have seen people using the following for loop
for (( i = 0; i <= $length; i++ ))
Can I omit the $ sign in double parentheses to refer the variable length?

Definitely you can do that as following:
length='10'
for (( i = 0; i <= length; i++ ))
do
echo "I = $i"
done
Output:
I = 0
I = 1
I = 2
I = 3
I = 4
I = 5
I = 6
I = 7
I = 8
I = 9
I = 10

Yes, in fact, ShellCheck encourages it.

As your shell interpreter is bash, if your length is known I would recommend you using a neat feature called bash sequence expression:
for each in {0..10}
do
echo $each
done

Related

Generate all possible ipv4 addresses using seq?

When using seq to generate an ip address, I use seq 0 255 and it generate the last octet. How can I transition this so it will generate all the other octets and their possible combinations (over 4 million combinations). Any help to start would be appreciated
If you were looking for a bash solution:
for h in {1..255}; do for i in {1..255}; do for j in {1..255}; do for k in {1..255}; do echo "$h.$i.$j.$k"; done; done; done; done
Or the multi-line version
for h in {1..255}
do for i in {1..255}
do for j in {1..255}
do for k in {1..255}
do echo "$h.$i.$j.$k"
done
done
done
done
Or if you are really intent on using seq
for h in `seq 255`; do for i in `seq 255`; do for j in `seq 255`; do for k in `seq 255`; do echo "$h.$i.$j.$k"; done; done; done; done
With awk and four loops:
awk 'BEGIN{OFS="."; for(h=0;h<256;h++){for(i=0;i<256;i++){for(j=0;j<256;j++){for(k=0;k<256;k++){print h,i,j,k}}}}}'
With C and four loops:
Put this a file with name ipgen.c:
#include <stdio.h>
int main() {
int h, i, j, k;
for (h = 0; h < 256; h++) {
for (i = 0; i < 256; i++) {
for (j = 0; j < 256; j++) {
for (k = 0; k < 256; k++) {
printf("%d.%d.%d.%d\n", h, i, j, k);
}
}
}
}
return 0;
}
Compile it: gcc ipgen.c -o ipgen
Start it: ./ipgen
For speed I'd vote for a 4-way awk/for loop (per Cyrus comment), but thought I'd look at a recursive function:
tuple () {
local level=$1 # number of tuples to generate
local max=$2 # assume tuples are numbered 1 to max
local in=$3 # current tuple from parent
local pfx="." # for all but the topmost call we append a period on the front of our loop counter
[[ -z "${in}" ]] && pfx="" # topmost call appends no prefix
local i
local out
for (( i=1 ; i<=${max} ; i++ ))
do
out="${in}${pfx}${i}"
if [[ "${level}" -eq 1 ]] # if we've reached the bottom of our function calls
then
echo "${out}" # print our latest string
else
tuple $((level-1)) "${max}" "${out}" # otherwise recurse with the latest string
fi
done
}
To build a 3-tuple with values ranging from 1 to 2:
$ tuple 3 2
1.1.1
1.1.2
1.2.1
1.2.2
2.1.1
2.1.2
2.2.1
2.2.2
To build a 3-tuple with values ranging from 1 to 4:
$ tuple 3 4
1.1.1
1.1.2
1.1.3
1.1.4
1.2.1
...
4.3.4
4.4.1
4.4.2
4.4.3
4.4.4
To build a 4-tuple with values ranging from 1 to 255 (keep in mind this is NOT going to be fast since we're making a LOT of bash-level calls):
$ tuple 4 255
1.1.1.1
1.1.1.2
1.1.1.3
1.1.1.4
1.1.1.5
1.1.1.6
1.1.1.7
1.1.1.8
1.1.1.9
1.1.1.10
... if you let it run long enough ...
255.255.255.250
255.255.255.251
255.255.255.252
255.255.255.253
255.255.255.254
255.255.255.255

Beginner Shell, can't find the issue (array sorting)

Working on a little script which put random numbers in a 10 000 size array and then sort all this array with the method ask during the course.
I've done this code but it seem that it begin to sort (when I test I have some "a" that are printed but not as much as supposed to and I don't understand why)
I'm believing the problem come fromes my test on val array, and it's probably a beginner error but I don't really know how to find the problem on th web as I don't really now which line is the problem.
I don't necessary need an answer, just some clues to find it could be good :)
Here is my code: (new to stackoverflow so I don't know how to put a good code view directly, if anyone can show me)
for i in `seq 1 10000`;
do
val[${i}]=$RANDOM
done
echo `date +"%M.%S.%3N"`
FLAG=0
until [ $FLAG -eq 1 ]
do
FLAG=1
for j in `seq 1 9999`;
do
if [ ${val[${j}]} -gt ${val[${j+1}]} ]
then
TMP=${val[${j}]}
val[${j}]=${val[${j+1}]}
val[${j+1}]=$TMP
FLAG=0
echo a
fi
done
done
echo `date +"%M.%S.%3N"`
as asked I can't really have a useful output as I just want the date before and after the sort operation. But the sort is just supposed to put values from lower to higher by taking them two by two and invert them if necessary. Doing this until no numbers are inverted.
Edit: I tried with manual number:
10 3 6 9 1
when running it by putting echo ${val[*]} in the for loop it just print 4 times the same list in the same order, so I'm guessing it doesn't work at all... Is my use of "if" wrong ?
Edit 2: At the begining, I did it in C# and I wanted to do it in shell then, firstly because I wanted to practice shell and then because I wanted to compare efficiency and time needed for the same thing. here is the C# code, working.
Random random = new Random();
int[] _tab = new int[100000];
for (int i = 0; i < _tab.Length; i++)
{
_tab[i] = random.Next(1, _tab.Length);
}
bool perm;
int tmp;
DateTime dt = DateTime.Now;
do
{
perm = false;
for (int i = 0; i < (_tab.Length - 1); i++)
{
if (_tab[i] > _tab[i + 1])
{
tmp = _tab[i];
_tab[i] = _tab[i + 1];
_tab[i + 1] = tmp;
perm = true;
}
}
}
while (perm == true);
Console.WriteLine((DateTime.Now - dt).TotalMilliseconds);
Console.Read();
Thanks :)
If my understanding that you want to know why this script is not producing an "a" indicating the ordering of the array of the numbers initially produced in the "for" loop is correct, then here is a solution:
The syntax is incorrect for your variable expansion. The ${var} cannot have math operators inside the braces, because they have different meaning here. In a normal non-associative array Zsh handles subscripts with some basic math support, so you can use ${array[var+1]} instead of ${array[${var+1}]} as you previously did.
I suspect the reason this came about - complicated, error prone POSIX syntax - would have been avoided by using simplified Zsh syntax, but as stated in an earlier comment, it would not be portable to other shells.
Some shells support similar features: Bash supports most, but not bare subscripts ($array[var]). Strings may be ordered in Zsh in a similar manner, but the math-context brackets (( and )) would have to be replaced with normal test brackets [[ and ]] and the array $val might have to be defined with special typeset options to make the strings compare in the desired manner; that is, they might have to be padded and right or left aligned. For comparing enumeration types, like Jan - Feb, it gets a little more complicated with associative arrays and case-conversion.
Here is the script with the appropriate changes, then again in simplified Zsh:
#!/bin/sh
for i in `seq 1 10000`;
do
val[$((i))]=$RANDOM
done
echo `date +"%M.%S.%3N"`
FLAG=0
until [ $FLAG -eq 1 ]
do
FLAG=1
for j in `seq 1 9999`;
do
if [ ${val[$((j))]} -gt ${val[$((j+1))]} ]
then
TMP=${val[$((j))]}
val[$((j))]=${val[$((j+1))]}
val[$((j+1))]=$TMP
FLAG=0
echo a
fi
done
done
echo `date +"%M.%S.%3N"`
Zsh:
#!/bin/zsh
foreach i ( {1..10000} )
val[i]=$RANDOM
end
echo `date +"%M.%S.%3N"`
FLAG=0
until ((FLAG))
do
FLAG=1
foreach j ( {1..9999} )
if (( val[j] > val[j+1] ))
then
TMP=$val[j]
val[j]=$val[j+1]
val[j+1]=$TMP
FLAG=0
echo a
fi
end
done
echo `date +"%M.%S.%3N"`

Manage Sub-variables in Bash

I want to manage subvariables in Bash. I can assign the subvariables, but I dont know how to use it:
#/bin/bash
n=1
for lvl in 1 2;
do
export key$n="${RANDOM:0:2}"
let n=$n+1
done
for num in 1 2; do
echo $key$num
done
If I use echo $key$num, it print number sequence of variable $num, and not the random numbers
Use arrays.
for n in 1 2; do
key[n]="${RANDOM:0:2}"
done
for num in 1 2; do
echo "${key[num]}"
done
See http://mywiki.wooledge.org/BashGuide/Arrays.
Also, in bash you'll generally do better counting from 0 instead of 1, and you don't need to export variables unless you want to run some other program that is going to look for them in its inherited environment.
You may use arrays (see #MarkReed), or use declare:
for n in 1 2; do
declare -- key$n="${RANDOM:0:2}"
done
for n in 1 2; do
v=$(declare -p key$n) ; v="${v#*=}" ; echo "${v//\"/}"
done
The same using functions:
key_set () # n val
{
declare -g -- key$1=$2
}
key_get () # n
{
local v=$(declare -p key$1) ; v="${v#*=}" ; echo "${v//\"/}"
}
for n in 1 2; do
key_set $n "${RANDOM:0:2}"
done
for n in 1 2; do
key_get $n
done

How to pad text in bash

I want to create a text file using bash shell in the following
+9990000001
+9990000002
+9990000003
+9990000004
...
The first four chars/digits are fixed (+999) and the other 7 digits starts from 0000001 to 9999999
I have the following code but it does not do 00000000 padding.
startnum=0;
endnum=9999999;
for (( i=$startnum; i<=$endnum; i++ )) ; do
echo $i ;
done
How would I also do the prefix +999?
You need to use printf to pad 0s:
startnum=1
endnum=9999999
for (( i=startnum; i<=endnum; i++ )) ; do
printf "+999%07d\n" $i
done
Output:
+9990000001
+9990000002
+9990000003
+9990000004
+9990000005
+9990000006
...
You can write:
startnum=90000000
endnum=99999999
for (( i = startnum; i <= endnum; i++ )) ; do
echo +99$i
done
(I would have suggested simply putting all three nines at the beginning, but 1010−1 is outside the range of 32-bit signed integers, so I'm not sure the arithmetic part would work.)
My version:
seq -f "+%10.0f" 9990000001 9999999999
or
seq -f "+999%07.0f" 9999999

More simple math help in bash!

In the same thread as this question, I am giving this another shot and ask SO to help address how I should take care of this problem. I'm writing a bash script which needs to perform the following:
I have a circle in x and y with radius r.
I specify resolution which is the distance between points I'm checking.
I need to loop over x and y (from -r to r) and check if the current (x,y) is in the circle, but I loop over discrete i and j instead.
Then i and j need to go from -r/resolution to +r/resolution.
In the loop, what will need to happen is echo "some_text i*resolution j*resolution 15.95 cm" (note lack of $'s because I'm clueless). This output is what I'm really looking for.
My best shot so far:
r=40.5
resolution=2.5
end=$(echo "scale=0;$r/$resolution") | bc
for (( i=-end; i<=end; i++ ));do
for (( j=-end; j<=end; j++ ));do
x=$(echo "scale=5;$i*$resolution") | bc
y=$(echo "scale=5;$j*$resolution") | bc
if (( x*x + y*y <= r*r ));then <-- No, r*r will not work
echo "some_text i*resolution j*resolution 15.95 cm"
fi
done
done
I've had just about enough with bash and may look into ksh like was suggested by someone in my last question, but if anyone knows a proper way to execute this, please let me know! What ever the solution to this, it will set my future temperament towards bash scripting for sure.
You may want to include the pipe into bc in the $()'s. Instead of.
end=$(echo "scale=0;$r/$resolution") | bc
use
end=$(echo "scale=0;$r/$resolution" | bc)
should help a bit.
EDIT And here's a solution.
r=40.5
resolution=2.5
end=$(echo "scale=0;$r/$resolution" | bc)
for i in $(seq -${end} ${end}); do
for j in $(seq -${end} ${end}); do
x=$(echo "scale=5;$i*$resolution" | bc)
y=$(echo "scale=5;$j*$resolution" | bc)
check=$(echo "($x^2+$y^2)<=$r^2" | bc)
if [ ${check} -eq '1' ]; then
iRes=$(echo "$i*$resolution" | bc)
jRes=$(echo "$j*$resolution" | bc)
echo "some_text $iRes $jRes 15.95 cm"
fi
done
done
As already mentioned this problem is probably best solved using bc, awk, ksh or another scripting language.
Pure Bash. Simple problems which actually need floating point arithmetic sometimes can be transposed to some sort of fixed point arithmetic using only integers. The following solution simulates 2 decimal places after the decimal point.
There is no need for pipes and external processes inside the loops if this precision is sufficient.
factor=100 # 2 digits after the decimal point
r=4050 # the representation of 40.50
resolution=250 # the representation of 2.50
end=$(( (r/resolution)*factor )) # correct the result of the division
for (( i=-end; i<=end; i+=factor )); do
for (( j=-end; j<=end; j+=factor )); do
x=$(( (i*resolution)/factor )) # correct the result of the division
y=$(( (j*resolution)/factor )) # correct the result of the division
if [ $(( x*x + y*y )) -le $(( r*r )) ] ;then # no correction needed
echo "$x $y ... "
fi
done
done
echo -e "resolution = $((resolution/factor)).$((resolution%factor))"
echo -e "r = $((r/factor)).$((r%factor))"
you haven't heard of (g)awk ??. then you should go learn about it. It will benefit you for the long run. Translation of your bash script to awk.
awk 'BEGIN{
r=40.5
resol=2.5
end = r/resol
print end
for (i=-end;i<=end;i++) {
for( j=-end;j<=end;j++ ){
x=sprintf("%.5d",i*resol)
y=sprintf("%.5d",j*resol)
if ( x*x + y*y <= r*r ){
print ".......blah blah ......"
}
}
}
}'
It's looking more like a bc script than a Bash one any way, so here goes:
#!/usr/bin/bc -q
/* -q suppresses a welcome banner - GNU extension? */
r = 40.5
resolution = 2.5
scale = 0
end = r / resolution
scale = 5
for ( i = -end; i <= end; i++ ) {
/* moved x outside the j loop since it only changes with i */
x = i * resolution
for ( j = -end; j <= end; j++ ) {
y = j * resolution
if ( x^2 * y^2 <= r^2 ) {
/*
the next few lines output on separate lines, the quote on
a line by itself causes a newline to be created in the output
numeric output includes newlines automatically
you can comment this out and uncomment the print statement
to use it which is a GNU extension
*/
/* */
"some_text
"
i * resolution
j * resolution
"15.95 cm
"
/* */
/* non-POSIX:
print "some_text ", i * resolution, " ", j * resolution, " 15.95 cm\n"
*/
}
}
}
quit

Resources