How do I generate random numbers in 3 lines - Linux Shell Script - linux

I would like to write a code that can generate 3 rows of 6 random numbers spaced out, which shuffle after a given time (0.5 seconds), and no new rows are created, basically 6 random numbers keep generating in 3 rows.
The code I have so far is:
echo " "
echo " "
echo " "
for i in {1..5};
do
for i in {1..1};
do
echo -ne " $(($RANDOM % 100)) $(($RANDOM % 100)) $(($RANDOM % 100)) $(($RANDOM % 100)) $(($RANDOM % 100)) $(($RANDOM % 100))\r"
done
sleep 0.5
done
However, when I try to add the second and third row to this, it doesn't seem to work the way I want it. A sample output could look like:
45 88 85 90 44 22
90 56 34 55 32 45
58 99 42 10 48 98
and between these numbers, new ones will generate, keeping only 6 columns and 3 rows. I have tried making matrix too but it didn't work for me.

I don't know if you have it finish, but continuing on from the comment, I would fill an indexed array with random values between 1-100, e.g.
#!/bin/bash
for ((i = 0; i < 18; i++)); do ## fill array with random values
a[i]=$(($RANDOM % 100 + 1))
done
What you would then want is a function you could call, passing the number of values in each row (so you can output a '\n' after those digits print) and then the array values as arguments to the function to read into a local array within the function (of course, you can just use the original array without worrying about passing elements as arguments, but I prefer using local values within function to preserve values in other scopes unchanged. For that your print function could be something like:
## function to print array in shuffled order, using tput for cursor control
prnarray() {
local n=$1
local al=( ${#:2} )
local c=0
for i in $(shuf -i 0-$((${#al[#]} - 1))); do
[ "$c" -gt '0' -a $((c % n)) -eq '0' ] && printf "\n"
printf " %3d" "${al[i]}"
((c++))
done
printf "\n"
tput cuu 6 ## tput is used for cursor control to move back to top
}
Then you really don't need much else bu a loop to print the array, sleep for some period of time and then call prnarray again to overlay the output with a new shuffle. e.g.
tput sc ## save cursor position
## call prnarray 3 times with 5 sec. delay between displays
declare -i c=0
while (( c < 3 )); do
prnarray 3 ${a[#]}
((c++))
sleep 5
done
tput rc ## restore cursor position
Example Use/Output
The array will print in the same spot every 5 seconds with the same elements shuffled to different positions within the array.
$ sh randomshuf.sh
33 30 34
86 98 48
94 89 80
50 57 34
11 45 57
80 42 22
Give it a shot and let me know if you have any questions.
Note: to make it 3x6 change the lines:
tput cuu 3
and
prnarray 6 ${a[#]}
With those changed your output would resemble:
$ sh randomshuf.sh
85 9 45 14 18 16
6 59 43 19 29 58
7 89 18 72 29 29

I would recommend you to avoid using the shell for this. The shell is great for automating system administration tasks - e.g interact with files, directories, shell commands -, but it is also great to keep people away from learning a 'real' and most powerful programming language.
python, ruby or perl can help you out.
if all you have is a hammer, everything looks like a nail.
e.g ruby
def print_random_numbers(num)
random_numbers = []
num.times do |n|
random_numbers << rand(100)
end
puts random_numbers.join(' ')
puts
end
while true
3.times do
print_random_numbers(6)
end
sleep 0.5
end

I'm not sure whether I really understand your problem, but:
This gets you 6 numbers taken at random from the range 1-100
numbers=$(shuf -i 1-100 -n 6)
echo $numbers
The numbers are selected without repetition. If you want repetition, use -r.
This gives you a permutation of the numbers drawn before:
echo $numbers | tr ' ' '\n' | shuf | xargs echo

Related

Illogical number priority in file names in BASH [duplicate]

This question already has answers here:
How to loop over files in natural order in Bash?
(7 answers)
Closed 1 year ago.
It so happens that I wrote a script in BASH, part of which is supposed to take files from a specified directory in numerical order. Obviously, files in that directory are named as follows: 1, 2, 3, 4, 5, etc. The thing is, I discovered that while running this script with 10 files in the directory, something that appears quite illogical to me, occurs, as the script takes files in strange order: 10, 1, 2, 3, etc.
How do I make it run from minimum value of name of a file to maximum in decimals?
Also, I am using the following line of code to define loop and path:
for file in /dir/*
Don't know if it matters, but I'm using Fedora 33 as OS.
Directories are sorted by alphabetical order. So "10" is before "2".
If I list 20 files whose names correspond to the 20 first integers, I get:
1 10 11 12 13 14 15 16 17 18 19 2 20 3 4 5 6 7 8 9
I can call the function 'sort -n' so I'll sort them numerically rather than alphabetically. The following command:
for i in $(ls | sort -n) ; do echo $i ; done
produces the following output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
i.e. your command:
for file in /dir/*
should be rewritten:
for file in "dir/"$(ls /dir/* | sort -n)
If you have GNU sort then use the -V flag.
for file in /dir/* ; do echo "$file" ; done | sort -V
Or store the data in an array.
files=(/dir/*); printf '%s\n' "${files[#]}" | sort -V
As an aside, if you have the option and work once ahead of time is preferable to sorting every time, you could also format the names of your directories with leading zeroes. This is frequently a better design when possible.
I made both for some comparisons.
$: echo [0-9][0-9]/ # perfect list based on default string sort
00/ 01/ 02/ 03/ 04/ 05/ 06/ 07/ 08/ 09/ 10/ 11/ 12/ 13/ 14/ 15/ 16/ 17/ 18/ 19/ 20/
That also filters out any non-numeric names, and any non-directories.
$: for d in [0-9][0-9]/; do echo "${d%/}"; done
00
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
If I show both single- and double-digit versions (I made both)
$: shopt -s extglob
$: echo #(?|??)
0 00 01 02 03 04 05 06 07 08 09 1 10 11 12 13 14 15 16 17 18 19 2 20 3 4 5 6 7 8 9
Only the single-digit versions without leading zeroes get out of order.
The shell sorts the names by the locale order (not necessarily the byte value) of each individual character. Anything that starts with 1 will go before anything that starts with 2, and so on.
There's two main ways to tackle your problem:
sort -n (numeric sort) the file list, and iterate that.
Rename or recreate the target files (if you can), so all numbers are the same length (in bytes/characters). Left pad shorter numbers with 0 (eg. 01). Then they'll expand like you want.
Using sort (properly):
mapfile -td '' myfiles <(printf '%s\0' * | sort -zn)
for file in "${myfiles[#]}"; do
# what you were going to do
sort -z for zero/null terminated lines is common but not posix. It makes processing paths/data that contains new lines safe. Without -z:
mapfile -t myfiles <(printf '%s\n' * | sort -n)
# Rest is the same.
Rename the target files:
#!/bin/bash
cd /path/to/the/number/files || exit 1
# Gets length of the highest number. Or you can just hardcode it.
length=$(printf '%s\n' * | sort -n | tail -n 1)
length=${#length}
for i in *; do
mv -n "$i" "$(printf "%.${length}d" "$i")"
done
Examples for making new files with zero padded numbers for names:
touch {000..100} # Or
for i in {000..100}; do
> "$i"
done
If it's your script that made the target files, something like $(printf %.Nd [file]) can be used to left pad the names before you write to them. But you need to know the length in characters of the highest number first (N).

How to add serial number in shell

I would like to add serial numbers to the following print statement.
for i in 1 5 20 50 100 200 500 1000;do
#I have here some computations for each i, finally mean_${i}=xyz
#for example
mean_1=10.42
mean_5=12.43
mean_20=25.34
mean_50=59.34
mean_100=150.32
mean_200=378.43
mean_500=697.45
mean_1000=1233.54
printf "%5s %10s %10s\n" sl.No. "$i" "mean_${i}" >> ofile.txt
done
I can't able to add the serial numbers.
Desired output
ofile.txt
1 1 10.42
2 5 12.43
3 20 25.34
4 50 59.34
5 100 150.32
6 200 378.43
7 500 697.45
8 1000 1233.54
Assuming you mean "line numbers" -- NR refers to the current line number in awk; however, while awk is frequently used from shell, it is its own independent programming language with its own syntax.
Maintaining an explicit counter is the typical practice, as in the case of ln below:
mean_1=10.42
mean_5=12.43
mean_20=25.34
mean_50=59.34
mean_100=150.32
mean_200=378.43
mean_500=697.45
mean_1000=1233.54
ln=0
for i in 1 5 20 50 100 200 500 100; do
meanvar=mean_$i
printf '%5s %10s %10s\n' "$((++ln))" "$i" "${!meanvar}"
done

using -sort in linux

I want to sort the input of the user with sort in a case (and function).
But I never used this before. Do I have to use an array or something?
For example the user does:
bash test.sh 50 20 35 50
Normally in my script this would happen:
ping c -1 "192.168.0.$i"
That results in
192.168.0.50
192.168.0.20
192.168.0.35
192.168.0.50
Now I want that the last numbers are sorted and also pinged from smallest to the biggest number like this: 20 35 50 and also that if you have 2 times the same number, the script only pings that number one time.
SortNumbers(){
}
...
case
-sort ) SortNumbers;;
esac
You can use this:
#!/bin/bash
array=($(printf '%s\n' "$#"|sort -nu))
echo ${array[#]}
If you run test.sh 34 1 45 1 5 6 6 6, it will give output:
1 5 6 34 45
Now you can use the variable $array with a for loop like:
for i in ${array[#]};do
#do something with $i
done
Explanation:
The arguments of the script is piped to the command sort and the output is assigned into an array named array. The options -n is for numerical sort and -u is for unique.
Assumed complete code for you (for clarification):
#!/bin/bash
array=($(printf '%s\n' "$#"|sort -nu))
for i in ${array[#]};do
ping -c -1 "192.168.0.$i"
done
Using a function:
sortNumbers(){
array=($(printf '%s\n' "$#"|sort -nu))
}
sortNumbers 43 1 2 8 2 4 98 45
echo ${array[#]} ##this is just a sample use, you can put for loop here
So you can declare an array array=($#) at the begining of your script. then call the sortNumbers function with the arguments (remember to exclude -sort from the argument) when needed to sort them (it will change the variable $array with sorted content). Put the for loop outside the function so it takes whatever in the variable $array (sorted or unsorted), that way you will have it your way (choice to do sort or not).
Try this:
#!/bin/bash
# 1. copy the scripts arguments into an array
array=($#)
# 2. Set internal field separator to newline
IFS=$'\n'
# 3. pass the array contents to sort's stdin using here-string
sorted=($(sort <<<"${array[*]}"))
# 4. pass the output of sort to uniq utility using the same technique
uniq=($(uniq <<<"${sorted[*]}"))
# 5. print the final array
printf "%s\n" "${uniq[#]}"
lcd047's shorter version:
IFS=$'\n' sorted=($(sort -nu <<<"$*"))
set "${sorted[#]}"
printf "%s\n" "$#"
Run result:
$> bash test.sh 3 2 1 45 45 3 4 4 4 1 1 1 1
1
2
3
4
45

AWK--Comparing the value of two variables in two different files

I have two text files A.txt and B.txt. Each line of A.txt
A.txt
100
222
398
B.txt
1 2 103 2
4 5 1026 74
7 8 209 55
10 11 122 78
What I am looking for is something like this:
for each line of A
search B;
if (the value of third column in a line of B - the value of the variable in A > 10)
print that line of B;
Any awk for doing that??
How about something like this,
I had some troubles understanding your question, but maybe this will give you some pointers,
#!/bin/bash
# Read intresting values from file2 into an array,
for line in $(cat 2.txt | awk '{print $3}')
do
arr+=($line)
done
# Linecounter,
linenr=0
# Loop through every line in file 1,
for val in $(cat 1.txt)
do
# Increment linecounter,
((linenr++))
# Loop through every element in the array (containing values from 3 colum from file2)
for el in "${!arr[#]}";
do
# If that value - the value from file 1 is bigger than 10, print values
if [[ $((${arr[$el]} - $val )) -gt 10 ]]
then
sed -n "$(($el+1))p" 2.txt
# echo "Value ${arr[$el]} (on line $(($el+1)) from 2.txt) - $val (on line $linenr from 1.txt) equals $((${arr[$el]} - $val )) and is hence bigger than 10"
fi
done
done
Note,
This is a quick and dirty thing, there is room for improvements. But I think it'll do the job.
Use awk like this:
cat f1
1
4
9
16
cat f2
2 4 10 8
3 9 20 8
5 1 15 8
7 0 30 8
awk 'FNR==NR{a[NR]=$1;next} $3-a[FNR] < 10' f1 f2
2 4 10 8
5 1 15 8
UPDATE: Based on OP's edited question:
awk 'FNR==NR{a[NR]=$1;next} {for (i in a) if ($3-a[i] > 10) print}'
and see how simple awk based solution is as compared to nested for loops.

How to extract every N columns and write into new files?

I've been struggling to write a code for extracting every N columns from an input file and write them into output files according to their extracting order.
(My real world case is to extract every 800 columns from a total 24005 columns file starting at column 6, so I need a loop)
In a simpler case below, extracting every 3 columns(fields) from an input file with a start point of the 2nd column.
for example, if the input file looks like:
aa 1 2 3 4 5 6 7 8 9
bb 1 2 3 4 5 6 7 8 9
cc 1 2 3 4 5 6 7 8 9
dd 1 2 3 4 5 6 7 8 9
and I want the output to look like this:
output_file_1:
1 2 3
1 2 3
1 2 3
1 2 3
output_file_2:
4 5 6
4 5 6
4 5 6
4 5 6
output_file_3:
7 8 9
7 8 9
7 8 9
7 8 9
I tried this, but it doesn't work:
awk 'for(i=2;i<=10;i+a) {{printf "%s ",$i};a=3}' <inputfile>
It gave me syntax error and the more I fix the more problems coming out.
I also tried the linux command cut but while I was dealing with large files this seems effortless. And I wonder if cut would do a loop cut of every 3 fields just like the awk.
Can someone please help me with this and give a quick explanation? Thanks in advance.
Actions to be performed by awk on the input data must be included in curled braces, so the reason the awk one-liner you tried results in a syntax error is that the for cycle does not respect this rule. A syntactically correct version will be:
awk '{for(i=2;i<=10;i+a) {printf "%s ",$i};a=3}' <inputfile>
This is syntactically correct (almost, see end of this post.), but does not do what you think.
To separate the output by columns on different files, the best thing is to use awk redirection operator >. This will give you the desired output, given that your input files always has 10 columns:
awk '{ print $2,$3,$4 > "file_1"; print $5,$6,$7 > "file_2"; print $8,$9,$10 > "file_3"}' <inputfile>
mind the " " to specify the filenames.
EDITED: REAL WORLD CASE
If you have to loop along the columns because you have too many of them, you can still use awk (gawk), with two loops: one on the output files and one on the columns per file. This is a possible way:
#!/usr/bin/gawk -f
BEGIN{
CTOT = 24005 # total number of columns, you can use NF as well
DELTA = 800 # columns per file
START = 6 # first useful column
d = CTOT/DELTA # number of output files.
}
{
for ( i = 0 ; i < d ; i++)
{
for ( j = 0 ; j < DELTA ; j++)
{
printf("%f\t",$(START+j+i*DELTA)) > "file_out_"i
}
printf("\n") > "file_out_"i
}
}
I have tried this on the simple input files in your example. It works if CTOT can be divided by DELTA. I assumed you had floats (%f) just change that with what you need.
Let me know.
P.s. going back to your original one-liner, note that the loop is an infinite one, as i is not incremented: i+a must be substituted by i+=a, and a=3 must be inside the inner braces:
awk '{for(i=2;i<=10;i+=a) {printf "%s ",$i;a=3}}' <inputfile>
this evaluates a=3 at every cycle, which is a bit pointless. A better version would thus be:
awk '{for(i=2;i<=10;i+=3) {printf "%s ",$i}}' <inputfile>
Still, this will just print the 2nd, 5th and 8th column of your file, which is not what you wanted.
awk '{ print $2, $3, $4 >"output_file_1";
print $5, $6, $7 >"output_file_2";
print $8, $9, $10 >"output_file_3";
}' input_file
This makes one pass through the input file, which is preferable to multiple passes. Clearly, the code shown only deals with the fixed number of columns (and therefore a fixed number of output files). It can be modified, if necessary, to deal with variable numbers of columns and generating variable file names, etc.
(My real world case is to extract every 800 columns from a total 24005 columns file starting at column 6, so I need a loop)
In that case, you're correct; you need a loop. In fact, you need two loops:
awk 'BEGIN { gap = 800; start = 6; filebase = "output_file_"; }
{
for (i = start; i < start + gap; i++)
{
file = sprintf("%s%d", filebase, i);
for (j = i; j <= NF; j += gap)
printf("%s ", $j) > file;
printf "\n" > file;
}
}' input_file
I demonstrated this to my satisfaction with an input file with 25 columns (numbers 1-25 in the corresponding columns) and gap set to 8 and start set to 2. The output below is the resulting 8 files pasted horizontally.
2 10 18 3 11 19 4 12 20 5 13 21 6 14 22 7 15 23 8 16 24 9 17 25
2 10 18 3 11 19 4 12 20 5 13 21 6 14 22 7 15 23 8 16 24 9 17 25
2 10 18 3 11 19 4 12 20 5 13 21 6 14 22 7 15 23 8 16 24 9 17 25
2 10 18 3 11 19 4 12 20 5 13 21 6 14 22 7 15 23 8 16 24 9 17 25
With GNU awk:
$ awk -v d=3 '{for(i=2;i<NF;i+=d) print gensub("(([^ ]+ +){" i-1 "})(([^ ]+( +|$)){" d "}).*","\\3",""); print "----"}' file
1 2 3
4 5 6
7 8 9
----
1 2 3
4 5 6
7 8 9
----
1 2 3
4 5 6
7 8 9
----
1 2 3
4 5 6
7 8 9
----
Just redirect the output to files if desired:
$ awk -v d=3 '{sfx=0; for(i=2;i<NF;i+=d) print gensub("(([^ ]+ +){" i-1 "})(([^ ]+( +|$)){" d "}).*","\\3","") > ("output_file_" ++sfx)}' file
The idea is just to tell gensub() to skip the first few (i-1) fields then print the number of fields you want (d = 3) and ignore the rest (.*). If you're not printing exact multiples of the number of fields you'll need to massage how many fields get printed on the last loop iteration. Do the math...
Here's a version that'd work in any awk. It requires 2 loops and modifies the spaces between fields but it's probably easier to understand:
$ awk -v d=3 '{sfx=0; for(i=2;i<=NF;i+=d) {str=fs=""; for(j=i;j<i+d;j++) {str = str fs $j; fs=" "}; print str > ("output_file_" ++sfx)} }' file
I was successful using the following command line. :) It uses a for loop and pipes the awk program into it's stdin using -f -. The awk program itself is created using bash variable math.
for i in 0 1 2; do
echo "{print \$$((i*3+2)) \" \" \$$((i*3+3)) \" \" \$$((i*3+4))}" \
| awk -f - t.file > "file$((i+1))"
done
Update: After the question has updated I tried to hack a script that creates the requested 800-cols-awk script dynamically ( a version according to Jonathan Lefflers answer) and pipe that to awk. Although the scripts looks good (for me ) it produces an awk syntax error. The question is, is this too much for awk or am I missing something? Would really appreciate feedback!
Update: Investigated this and found documentation that says awk has a lot af restrictions. They told to use gawk in this situations. (GNU's awk implementation). I've done that. But still I'll get an syntax error. Still feedback appreciated!
#!/bin/bash
# Note! Although the script's output looks ok (for me)
# it produces an awk syntax error. is this just too much for awk?
# open pipe to stdin of awk
exec 3> >(gawk -f - test.file)
# verify output using cat
#exec 3> >(cat)
echo '{' >&3
# write dynamic script to awk
for i in {0..24005..800} ; do
echo -n " print " >&3
for (( j=$i; j <= $((i+800)); j++ )) ; do
echo -n "\$$j " >&3
if [ $j = 24005 ] ; then
break
fi
done
echo "> \"file$((i/800+1))\";" >&3
done
echo "}"

Resources