bash 4.2 / 4.3: Different behavior in C-style loop - bash

bash 4.2 show the assumed correct behavior in a C-style for loop:
me#server:/some/dir# TIMES=30; for (( n=0; n<$(shuf -i ${TIMES}-$(expr ${TIMES} + 20) -n 1); n++ )); do echo $n; done
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
me#server:/some/dir# bash --version
GNU bash, Version 4.2.25(1)-release (x86_64-pc-linux-gnu)
(...)
me#server:/some/dir#
The same under bash 4.3 throws an error:
me#server:/some/dir# TIMES=30; for (( n=0; n<$(shuf -i ${TIMES}-$(expr ${TIMES} + 20) -n 1); n++ )); do echo $n; done
-bash: syntax error near unexpected token `newline'
me#server:/some/dir# bash --version
GNU bash, Version 4.3.30(1)-release (x86_64-pc-linux-gnu)
(...)
Yet the part to find a random number between ${TIMES} and ${TIMES}+20 works:
me#server:/some/dir# shuf -i 20-50 -n 1
26
me#server:/some/dir#
So does inserting the numeral directly instead of $()-subshell'ing it:
me#server:/some/dir# TIMES=30; for (( n=0; n<26; n++ )); do echo $n; done
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
me#server:/some/dir#
What's going on here? Any ideas why the subshell is not executed correctly under bash 4.3?

If you replace $(expr with $((, it starts to work:
TIMES=30
for (( n=0; n < $(shuf -i $TIMES-$((TIMES + 20)) -n 1); n++ )) ; do
echo $n
done

Related

How to find the max number in shell

In my project, I want to find the max number with shell.
here is my shell code:
jobnuminit=0
for i in $(cat jobidtmp)
do
jobnum=`bjobs -l $i | grep Start| awk '{print $8}'`
echo $jobnum
if [ $jobnum -gt $jobnuminit ]
then
jobnuminit=$jobnum
fi
done
echo $jobnuminit
And the value of jobnum should be:
144
144
8
8
12
8
8
8
8
8
8
12
8
8
8
8
12
12
20
12
12
8
8
And the value of jobnuminit should be 144, but the result is 8.
I have tried:
if [ $jobnum > $jobnuminit ]
But it failed again, Who can help me?
Well, you can use just sort:
sort -n file | tail -1

How to find values 2 exponential in shell?

Is there a way to find a value's 2 exponential form in bash.
For example if I input 512 it should result output as 9 meaning 2 ^ 9 is 512.
Any help here is immensely appreciated - Thanks
When I read the question, 512 is the input, and 9 is the output. Is is possible what is being asked here is the answer to "log_base_2(512)" which has an answer of "9". If so, then maybe this would help.
$ echo "l(512) / l(2)" | bc -l
9.00000000000000000008
The explanation of the math can be found here:
How do I calculate the log of a number using bc?
Using awk.
$ echo 512 | awk '{print log($1)/log(2)}'
9
Put that into a script (expo.sh):
#!/bin/bash
_num="$1"
expon=$(awk -v a="$_num" 'BEGIN{print log(a)/log(2)}')
if [[ $expon =~ ^[0-9]+\.[0-9]*$ ]]; then # Match floating points
echo "$_num is not an exponent of 2"; # Not exponent if floating point
else
echo "$_num = 2^${expon}"; # print number
fi
Run:
$ ./expo.sh 512
512 = 2^9
$ ./expo.sh 21
21 is not an exponent of 2
A fast way to check a number x is an 2 exponent is to check bitwise and x and x-1 and to exclude 0, x>0
((x>0 && ( x & x-1 ) == 0 )) && echo $x is a 2-exponent
using this algorithm: fast-computing-of-log2-for-64-bit-integers to compute log2
tab32=( 0 9 1 10 13 21 2 29
11 14 16 18 22 25 3 30
8 12 20 28 15 17 24 7
19 27 23 6 26 5 4 31 )
log2_32() {
local value=$1
(( value |= value >> 1 ))
(( value |= value >> 2 ))
(( value |= value >> 4 ))
(( value |= value >> 8 ))
(( value |= value >> 16 ))
log2_32=${tab32[(value * 16#7C4ACDD & 16#ffffffff)>>27]}
}
log2_32 262144
echo "$log2_32"

change array into table bash scripting

how to change array into tabular form?
eg:
array = 1 2 3 4 5 6 7 8 9 10 11 12
result
1 10 11
2 9 12
3 8
4 7
5 6
in this particular order up-down-down-up-up-down
the array is taken from a .txt file and its based on user input so the value varies
here is some of my code
declare -a myarray
# Load file into array.
readarray myarray < temp2.txt
s=$myarray
f or i in $(seq 0 $((${#s} - 1))); do
echo "s[$i] = \"${s:$i:1}\""
done
This script does what you want:
#!/bin/bash
a=( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 )
rows=5
for (( j=0; j<rows; ++j )); do
for (( i=0; i<=$(( ${#a[#]} / rows )); ++i )); do
if (( i%2 )); then idx=$(( (i + 1) / 2 * 2 * rows - j - 1 ))
else idx=$(( (i / 2) * 2 * rows + j )); fi
printf "%-4s" "${a[idx]}"
done
printf "\n"
done
Output:
1 10 11
2 9 12
3 8 13 18
4 7 14 17
5 6 15 16
To make it work from left to right rather than from top to bottom, you can simply swap the i and j loops around (and change the name rows to cols so that it still makes sense):
#!/bin/bash
a=( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 )
cols=5
for (( i=0; i<=$(( ${#a[#]} / cols )); ++i )); do
for (( j=0; j<cols; ++j )); do
if (( i%2 )); then idx=$(( (i + 1) / 2 * 2 * cols - j - 1 ))
else idx=$(( (i / 2) * 2 * cols + j )); fi
printf "%-4s" "${a[idx]}"
done
printf "\n"
done
Output:
1 2 3 4 5
10 9 8 7 6
11 12 13 14 15
18 17 16
declare -a s
s=(1 2 3 4 5 6 7 8 9 10 11 12)
ofstabled=(9 7 5 3 1)
ofstableu=(1 3 5 7 9)
for ((i=0; i<5;++i)); do
for ((j=$i; j<${#s[#]};)); do
printf "%d " ${s[$j]}
let j=$j+${ofstabled[$i]}
if [ $j -lt ${#s[#]} ]; then
printf "%d " ${s[$j]}
let j=$j+${ofstableu[$i]}
fi
done
printf "\n"
done
You can gussy it up if you want but it works.
Script:
#!/bin/bash
a=(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18)
rows=${1:-5}
col=()
for ((i = 0; i < ${#a[#]}; i++)); do
((ind=i % rows))
(( ((i / rows) % 2 == 0) && (ind = ((rows - 1) - ind)) ))
[ -n "${col[(rows - 1) - $ind]}" ] && col[(rows - 1) - $ind]+=" "
col[(rows - 1) - $ind]+=${a[$i]}
done
printf %s\\n "${col[#]}" | column -t
Output:
$ ./order.sh
1 10 11
2 9 12
3 8 13 18
4 7 14 17
5 6 15 16
$ ./order.sh 3
1 6 7 12 13 18
2 5 8 11 14 17
3 4 9 10 15 16
With credit to #TomFenech for the inspiration to make the row count controllable.
You can use awk to read from the file and print it the way you want:
$ awk '{
if(NR<=5) {
a[NR]=$0
next
} else {
row=NR%10
}
}
!(row in a) {
if(row == 0)
row++
else
row=5 - (NR % 5) + 1
}
{
a[row]=a[row] FS $0
}
END {
for(i in a)
print a[i]
}' file
1 10 11
2 9 12
3 8
4 7
5 6

Bash script number generator

I need to generate random numbers in an specific format as test data. For example, given a number "n" I need to produce "n" random numbers and write them in a file. The file must contain at most 3 numbers per line. Here is what I have:
#!/bin/bash
m=$1
output=$2
for ((i=1; i<= m; i++)) do
echo $((RANDOM % 29+2)) >> $output
done
This outputs the numbers as:
1
2
24
21
10
14
and what I want is:
1 2 24
21 10 14
Thank you for your help!
Pure bash (written as a function rather than a script file)
randx3() {
local d=$' \n'
local i
for ((i=0;i<$(($1 - 1));++i)); do
printf "%d%c" $((RANDOM%29 + 2)) "${d:$((i%3)):1}"
done
printf "%d\n" $((RANDOM%29 + 2))
}
Note that it doesn't take a file argument; rather it outputs to stdout, so you would use it like this:
randx3 11 > /path/to/output
That style is often more flexible.
Here's a less hacky one which allows you to select how often you want a newline:
randx() {
local i
local m=$1
local c=${2:-3}
for ((i=1;i<=m;++i)); do
if ((i%c && i<m)); then
printf "%d " $((RANDOM%29 + 2))
else
printf "%d\n" $((RANDOM%29 + 2))
fi
done
}
Call that one as randx 11 or randx 11 7 (second argument defaults to 3).
Pipe the output to a command that will read 3 lines at a time:
for ((i=1; i<= m; i++)) do
echo $((RANDOM % 29+2))
done | sed -e '$!N;$!N;s/\n/ /g' >> $output
This is what paste was designed for:
$ for i in {0..10}; do echo $RANDOM; done | paste -d' ' - - -
14567 3240 16354
17457 25616 12772
3912 7490 12206
7342 10554
Another approach would be to build up the values in an array, then use printf.
m=$1
output=$2
vals=()
while (( m-- )); do
vals+=( $((RANDOM % 29+2)) )
done
printf '%d %d %d\n' "${vals[#]}" > "$output"
Shortest!!!
I need to produce "n" random numbers and write them in a file. The file must contain at most 3 numbers per line.
pr -t -3 -s\ <(for ((n=6;n--;)){ echo $((RANDOM % 29+2));}) >file
Then
cat file
11 29 27
14 21 22
YAS: Yet another bash solution
As a script:
#!/bin/bash
n=$1
file=$2
out=()
>$file
for ((i=1;i<=n;i++));do
out+=($((RANDOM%29+2)))
[ $((i%3)) -eq 0 ] && echo ${out[*]} >>$file && out=()
done
[ "$out" ] && echo ${out[*]} >>$file
Usage:
script <quantity of random> <filename>
Important remark about RANDOM%29
This way of rendering random between 2 to 30 is not equitable!
As $RANDOM give a number between 0 and 32767, there is:
for ((i=0;i<32768;i++)) ;do
((RL[$((i%29+2))]++))
done
for ((i=0;i<32;i++));do
printf "%3d %5d\n" $i ${RL[i]}
done | column
0 0 7 1130 14 1130 21 1130 28 1130
1 0 8 1130 15 1130 22 1130 29 1129
2 1130 9 1130 16 1130 23 1130 30 1129
3 1130 10 1130 17 1130 24 1130 31 0
4 1130 11 1130 18 1130 25 1130
5 1130 12 1130 19 1130 26 1130
6 1130 13 1130 20 1130 27 1130
... there is 1130 chances to obtain a number between 2 to 28, but only 1129 chances to obtain a 29 or a 30.
To prevent this, you have to drop unwanted results:
random2to30() {
local _random=32769
while (( $_random>=32741 )) ;do
_random=$RANDOM;
done;
printf -v $1 "%d" $((2+_random%29))
}
The proof:
tstr2to30() {
unset $1
local _random=32769
while (( $_random>=32741 )); do
read _random || break
done
[ "$_random" ] && printf -v $1 "%d" $((2 +_random % 29 ))
}
unset RL
while tstr2to30 MyRandom && [ "$MyRandom" ] ;do
((RL[MyRandom]++))
done < <(seq 0 32767)
for ((i=0;i<32;i++));do
printf "%3d %5d\n" $i ${RL[i]}
done | column
Give:
0 0 7 1129 14 1129 21 1129 28 1129
1 0 8 1129 15 1129 22 1129 29 1129
2 1129 9 1129 16 1129 23 1129 30 1129
3 1129 10 1129 17 1129 24 1129 31 0
4 1129 11 1129 18 1129 25 1129
5 1129 12 1129 19 1129 26 1129
6 1129 13 1129 20 1129 27 1129
Where all value do obtain exactly same (1129) chances!
Final useable script
So the script could become (Don't forget bash's shebang!):
#!/bin/bash
n=${1:-11} # default to 11 values
c=${2:-3} # default to 3 values by lines
minval=${3:-2} # default to 2 random min
maxval=${4:-30} # defailt to 30 random max
file=${5:-/dev/stdout} # default to STDOUT
rnum=$(( maxval - minval + 1 ))
rmax=$(( ( 32768 / rnum ) * rnum ))
randomGen() {
local _random=33000
while [ $_random -ge $rmax ] ;do
_random=$RANDOM
done
printf -v $1 "%d" $(( minval +_random % rnum ))
}
out=()
for ((i=1;i<=n;i++));do
randomGen MyRandom
out+=($MyRandom)
[ $((i%c)) -eq 0 ] && echo ${out[*]} >>"$file" && out=()
done
[ "$out" ] && echo ${out[*]} >>"$file"
This awk will insert a newline after every 3rd line or a space:
for ((i=1; i<= m; i++)); do
echo $((RANDOM % 29+2))
done | awk '{printf "%s%c", $1, (NR % 3) ? " " : "\n"}' >> $output
yet another way of doing it :
eval echo {1..$m} | xargs -n3 echo $((RANDOM % 29+2)) > $output

split a file based upon line number

I have a large file that needs to be slitted based on line numbers.
For instance , my file is like that:
aaaaaa
bbbbbb
cccccc
dddddd
****** //here blank line//
eeeeee
ffffff
gggggg
hhhhhh
*******//here blank line//
ıııııı
jjjjjj
kkkkkk
llllll
******
//And so on...
I need two separate files as such that one file should have first 4 lines, third 4 lines, fifth 4 lines in it and the other file should have second 4 lines, fourth 4 lines, sixth 4 lines in it and so on. how can I do that in bash script?
You can play with the number of the line, NR:
$ awk 'NR%10>0 && NR%10<5' your_file > file1
$ awk 'NR%10>5' your_file > file2
If it is 10K + n, 0 < n < 5, then goes to the first file.
If it is 10K + n, n > 5, then goes to the second file.
In one line:
$ awk 'NR%10>0 && NR%10<5 {print > "file1"} NR%10>5 {print > "file2"}' file
Test
$ cat a
1
2
3
4
6
7
8
9
11
12
13
14
16
17
18
19
21
22
23
24
26
27
28
29
31
32
33
34
36
37
38
39
41
42
43
44
46
47
48
49
51
$ awk 'NR%10>0 && NR%10<5 {print > "file1"} NR%10>5 {print > "file2"}' a
$ cat file1
1
2
3
4
11
12
13
14
21
22
23
24
31
32
33
34
41
42
43
44
51
$ cat file2
6
7
8
9
16
17
18
19
26
27
28
29
36
37
38
39
46
47
48
49
You can do this with head and tail (which are not be part of the bash itself):
head -n 20 <file> | tail -n 5
gives you the lines 15 to 20.
This is however inefficient, if you want to get multiple sections of your file, since it has to be parsed again and again. In this case I'd prefer some real scripting.
Another approach is to treat blank-line-separated paragraphs as the records, and print odd-numbered and even-numbered records to different files:
awk -v RS= -v ORS='\n\n' '{
outfile = (NR % 2 == 1) ? "file1" : "file2"
print > outfile
}' file
Maybe something like that:
#!/bin/bash
EVEN="even.log"
ODD="odd.log"
line_count=0
block_count=0
while read line
do
# ignore blank lines
if [ ! -z "$line" ]; then
if [ $(( $block_count % 2 )) -eq 0 ]; then
# even
echo "$line" >> "$EVEN"
else
# odd
echo "$line" >> "$ODD"
fi
line_count=$[$line_count +1]
if [ "$line_count" -eq "4" ]; then
block_count=$[$block_count +1]
line_count=0
fi
fi
done < "$1"
The first argument is the source file: ./split.sh split_input
This script prints lines from file 1.txt with indexes 0, 1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19, ...
i=0
while read p; do
if [ $i%8 -lt 4 ]
then
echo $p
fi
let i=$i+1
done < 1.txt
This script prints lines with indexes 4, 5, 6, 7, 12, 13, 14, 15, ...
i=0
while read p; do
if [ $i%8 -gt 3 ]
then
echo $p
fi
let i=$i+1
done < 1.txt

Resources