Generate combinations of elements with echo - bash

I need to prepare a simple script to generate all the permutations possible of a set of elements stored in a variable in groups of n elements (being n parameterizable), the easiest solution which came to mind was using several loops depending on the selected length of the group. But I thought that it would be more elegant taking advantage of the ability of echo command to generate combinations, that is
echo {1,2}{1,2}
11 12 21 22
So using this method, I'm trying to achieve a general way to do it, using as input parameters the list of elements (for example {1,2}) and the number of elements. It would be something like it:
set={1,2,3,4}
group=3
for ((i=0; i<$group; i++));
do
repetition=$set$repetition
done
So in this particular case, at the end of the loop the repetition variable has the value {1,2,3,4}{1,2,3,4}{1,2,3,4}. But I'm not able to find the way to use this variable to produce the combinations using the echo command. I've tried, several things like:
echo $repetition
echo $(echo $repetition)
I'm stucked on it, I'd appreciate any tip or help on that.

You can use:
bash -c "echo "$repetition""
111 112 113 114 121 122 123 124 131 132 133 134 141 142 143 144 211 212 213 214 221 222 223 224 231 232 233 234 241 242 243 244 311 312 313 314 321 322 323 324 331 332 333 334 341 342 343 344 411 412 413 414 421 422 423 424 431 432 433 434 441 442 443 444
Or else use eval instead of bash -c

If you need k-combinations for all k, this combination script can help:
#!/bin/bash
POWER=$((2**$#))
BITS=`seq -f '0' -s '' 1 $#`
while [ $POWER -gt 1 ];do
POWER=$(($POWER-1))
BIN=`bc <<< "obase=2; $POWER"`
MASK=`echo $BITS | sed -e "s/0\{${#BIN}\}$/$BIN/" | grep -o .`
POS=1; AWK=`for M in $MASK;do
[ $M -eq 1 ] && echo -n "print \\$\${POS};"
POS=$(($POS+1))
done;echo`
awk -v ORS=" " "{$AWK}" <<< "$#" | sed 's/ $//'
done
Example:
./combination ⚪ ⛔ ⚫
⚪ ⛔ ⚫
⚪ ⛔
⚪ ⚫
⚪
⛔ ⚫
⛔
⚫
The empty set is there too, trust me.

Related

Inverse modulo operations in bash

I am trying to write a bash script that will be able to do inverse modulo operations.
Currently the program can do regular mod calculations but trying to do inverse modulo operations leaves me with wrong answers (either prints 0's or wrong answers).
Is it even possible to do inverse calculations, if so what would be the formula?
Expected Result:
28 14 22 30 18 32 30 12 25 37 8 31 18 4 37 3 33 35 27 2 4 3 28
Gotten Results:
-23 -4 -29 -27 -17 -10 -27 -25 -24 -11 -37 -5 -17 -32 -11 -15 -6 -35 -39 -22
#!/bin/bash
flag_enc=(268 413 110 190 426 419 108 229 310 379 323 373 385 236 92 96 169 321 284 185 154 137 186) #inputs
for i in "${flag_enc[#]}"
do
mod=$((1 / $i)) # input^(-1)
modus=$(($mod % 41)) #mod41 calculation
echo "$modus"
done
Oguz Ismail gave the right mathematical expression for calculating inverse mod in bash. I am attaching the modified the script bellow:
flag_enc=(268 413 110 190 426 419 108 229 310 379 323 373 385 236 92 96 169 321 284 185 154 137 186)
m=41
for i in "${flag_enc[#]}"
do
for ((x = 1; x < m; x++)); do
if ((($i % m) * (x % m) % m == 1)); then
echo $x
fi
done
done
I don't know if your algorithm is correct or not, but bash doesn't support floating point arithmetic intrinsically.
So either use bc, or define a function like below
mmi() {
a=$1
m=$2
for ((x = 1; x < m; x++)); do
if (((a % m) * (x % m) % m == 1)); then
echo $x
return 0
fi
done
return 1
}
and use it like so:
$ mmi 268 41
28
Better use a dedicated tool or programming language. Example with the gmpy2 module of python (without error handling, this is left as an exercise):
for i in "${flag_enc[#]}"; do
mod=$(printf 'import gmpy2\nprint(int(gmpy2.invert(%d, %d)))\n' "$i" "41" | python)
echo "$mod"
done

need help in formatted output using for loop in shell script

I have the requirement to capture pod memory and other details in my setup.
have written small reproducible shell script as copied below.
LOGFILE='/root/User1/test/log'
Data=""
space=" "
e=34
f=12Mi
a=122
b=123
c=333
d=450
for i in {1..10}; do
Data+=$space
Data+=$a
Data+=$space
Data+=$b
Data+=$space
Data+=$c
Data+=$space
Data+=$d
Data+=$space
Data+=$e
Data+=$space
Data+=$f
printf "%s" "$Data" >> ${LOGFILE}
echo $'\n' >> ${LOGFILE}
$(unset ${Data})
done
The above script produces concatenated output.
34 12Mi 122 123 333 450
34 12Mi 122 123 333 450 34 12Mi 122 123 333 450
34 12Mi 122 123 333 450 34 12Mi 122 123 333 450 34 12Mi 122 123 333 450
34 12Mi 122 123 333 450 34 12Mi 122 123 333 450 34 12Mi 122 123 333 450 34 12Mi 122 123 333 450
34 12Mi 122 123 333 450 34 12Mi 122 123 333 450 34 12Mi 122 123 333 450 34 12Mi 122 123 333 450 34 12Mi 122 123 333 450
34 12Mi 122 123 333 450 34 12Mi 122 123 333 450 34 12Mi 122 123 333 450 34 12Mi 122 123 333 450 34 12Mi 122 123 333 450 34 12Mi 122
The output format what I am looking for is
34 12Mi 122 123 333 450
34 12Mi 122 123 333 450
34 12Mi 122 123 333 450
34 12Mi 122 123 333 450
34 12Mi 122 123 333 450
34 12Mi 122 123 333 450
34 12Mi 122 123 333 450
34 12Mi 122 123 333 450
can some one help me here to understand what mistake I am doing here.
and what could be the possible solution for this.
When you do $(unset ${Data}), you are running unset ${Data} in a subshell, and then try to run its output (the empty string) as a command. This is wrong on a few levels:
A subshell can't affect its parent environment, and you don't want to run the output as a command anyway
unset takes the name of the variable, not its expansion, as a parameter
The quick fix is to replace $(unset ${Data}) with unset Data.
A simpler overall approach could be to skip the intermediate variable entirely, and move the redirection out of the loop:
for i in {1..10}; do
printf '%s ' "$e" "$f" "$a" "$b" "$c"
printf '%s\n\n' "$d"
done >> "$LOGFILE"
This doesn't require $Data or $space any longer.
This prints the exact desired output you show, though the actual output you show doesn't correspond to your script, which has each line begin with three blanks. To get that, the printf formatting strings would have to be ' %s' and ' %s\n\n', respectively.

How to replace list of numbers in column for random numbers in other column in BASH environment

I have a tab file with two columns like that
5 6 14 22 23 25 27 84 85 88 89 94 95 98 100 6 94
6 8 17 20 193 205 209 284 294 295 299 304 305 307 406 205 284 307 406
2 10 13 40 47 58 2 13 40 87
and the desired output should be
5 6 14 22 23 25 27 84 85 88 89 94 95 98 100 14 27
6 8 17 20 193 205 209 284 294 295 299 304 305 307 406 6 209 299 305
2 10 13 23 40 47 58 87 10 23 40 58
I would like to change the numbers in 2nd column for random numbers in 1st column resulting in an output in 2nd column with the same number of numbers. I mean e.g. if there are four numbers in 2nd column for x row, the output must have four random numbers from 1st column for this row, and so on...
I'm try to create two arrays by AWK and split and replace every number in 2nd column for numbers in 1st column but not in a randomly way. I have seen the rand() function but I don't know exactly how joint these two things in a script. Is it possible to do in BASH environment or are there other better ways to do it in BASH environment? Thanks in advance
awk to the rescue!
$ awk -F'\t' 'function shuf(a,n)
{for(i=1;i<n;i++)
{j=i+int(rand()*(n+1-i));
t=a[i]; a[i]=a[j]; a[j]=t}}
function join(a,n,x,s)
{for(i=1;i<=n;i++) {x=x s a[i]; s=" "}
return x}
BEGIN{srand()}
{an=split($1,a," ");
shuf(a,an);
bn=split($2,b," ");
delete m; delete c; j=0;
for(i=1;i<=bn;i++) m[b[i]];
# pull elements from a upto required sample size,
# not intersecting with the previous sample set
for(i=1;i<=an && j<bn;i++) if(!(a[i] in m)) c[++j]=a[i];
cn=asort(c);
print $1 FS join(c,cn)}' file
5 6 14 22 23 25 27 84 85 88 89 94 95 98 100 85 94
6 8 17 20 193 205 209 284 294 295 299 304 305 307 406 20 205 294 295
2 10 13 23 40 47 58 87 10 13 47 87
shuffle (standard algorithm) the input array, sample required number of elements, additional requirement is no intersection with the existing sample set. Helper structure map to keep existing sample set and used for in tests. The rest should be easy to read.
Assuming that there is a tab delimiting the two columns, and each column is a space delimited list:
awk 'BEGIN{srand()}
{n=split($1,a," ");
m=split($2,b," ");
printf "%s\t",$1;
for (i=1;i<=m;i++)
printf "%d%c", a[int(rand() * n) +1], (i == m) ? "\n" : " "
}' FS=\\t input
Try this:
# This can be an external file of course
# Note COL1 and COL2 seprated by hard TAB
cat <<EOF > d1.txt
5 6 14 22 23 25 27 84 85 88 89 94 95 98 100 6 94
6 8 17 20 193 205 209 284 294 295 299 304 305 307 406 205 284 307 406
2 10 13 40 47 58 2 13 40 87
EOF
# Loop to read each line, not econvert TAB to:, though could have used IFS
cat d1.txt | sed 's/ /:/' | while read LINE
do
# Get the 1st column data
COL1=$( echo ${LINE} | cut -d':' -f1 )
# Get col1 number of items
NUM_COL1=$( echo ${COL1} | wc -w )
# Get col2 number of items
NUM_COL2=$( echo ${LINE} | cut -d':' -f2 | wc -w )
# Now split col1 items into an array
read -r -a COL1_NUMS <<< "${COL1}"
COL2=" "
# THis loop runs once for each COL2 item
COUNT=0
while [ ${COUNT} -lt ${NUM_COL2} ]
do
# Generate a random number to use as teh random index for COL1
COL1_IDX=${RANDOM}
let "COL1_IDX %= ${NUM_COL1}"
NEW_NUM=${COL1_NUMS[${COL1_IDX}]}
# Check for duplicate
DUP_FOUND=$( echo "${COL2}" | grep ${NEW_NUM} )
if [ -z "${DUP_FOUND}" ]
then
# Not a duplicate, increment loop conter and do next one
let "COUNT = COUNT + 1 "
# Add the random COL1 item to COL2
COL2="${COL2} ${COL1_NUMS[${COL1_IDX}]}"
fi
done
# Sort COL2
COL2=$( echo ${COL2} | tr ' ' '\012' | sort -n | tr '\012' ' ' )
# Print
echo ${COL1} :: ${COL2}
done
Output:
5 6 14 22 23 25 27 84 85 88 89 94 95 98 100 :: 88 95
6 8 17 20 193 205 209 284 294 295 299 304 305 307 406 :: 20 299 304 305
2 10 13 40 47 58 :: 2 10 40 58

Iterate over two items in bash

Given a file integers that contains integers separated by new lines. For instance:
1
39
77
109
137
169
197
229
261
293
One can iterate over the file using the following code:
while read a
do
echo "$a"
done < integers
I'm looking however for an elegant solution such that the loop takes two integers at once and always updates by one step, such that:
while #some funny commands
do
echo "$a | $b"
done < integers
results in:
1 | 39
39 | 77
77 | 109
109 | 137
137 | 169
169 | 197
197 | 229
229 | 261
261 | 293
{
read a
while read b; do
echo "$a | $b"
a=$b
done
} < file
Output:
1 | 39
39 | 77
77 | 109
109 | 137
137 | 169
169 | 197
197 | 229
229 | 261
261 | 293
Use a variable to store the previous value:
prev=
while read line; do
[[ ! -z $prev ]] && echo $prev "|" $line;
prev=$line;
done <file

Creating a sequence of distinct random numbers within a certain range in bash script

I have a file which contains entries numbered 0 to 149. I am writing a bash script which randomly selects 15 out of these 150 entries and create another file from them.
I tried using random number generator:
var=$RANDOM
var=$[ $var % 150 ]
Using var I picked those 15 entries. But I want all of these entries to be different. Sometimes same entry is getting picked up twice. Is there a way to create a sequence of random numbers within a certain range, (in my example, 0-149) ?
Use shuf -i to generate a random list of numbers.
$ entries=($(shuf -i 0-149 -n 15))
$ echo "${entries[#]}"
55 96 80 109 46 58 135 29 64 97 93 26 28 116 0
If you want them in order then add sort -n to the mix.
$ entries=($(shuf -i 0-149 -n 15 | sort -n))
$ echo "${entries[#]}"
12 22 45 49 54 66 78 79 83 93 118 119 124 140 147
To loop over the values, do:
for entry in "${entries[#]}"; do
echo "$entry"
done

Resources