Bash: identify repeating string in an array - bash

I have a script that detects crop values before I start video encoding.
I do several probes at a certain interval, say every 100th frame.
If all the crop values for all the probed frames match then there is no problem and the script kicks in the encoding function. If crop values differ then script exits with an error and I need to pick the correct crop value manually which is annoying.
So, instead of exiting the script with an error I would rather pick the "best" possible value which, in this situation, is the number that repeats the most.
So how do I pick a string out of the collection that repeats the most?
Say, I put all the crop values in an array.
Crop=('3' '4' '3' '5' '7' '3' '7');
So in this situation I would pick value '3' as it repeats most often.
How can I do it programmatically in Bash?
Thanks.
-- EDIT --
I do apologize, to simplify my question I might have confused some of you.
The real crop values look like this "720:568:0:4".

Pure bash solution using an associative array (bash version 4 needed):
#! /bin/bash
crop=(3 4 3 5 7 3 7)
declare -A count
max=0
for c in "${crop[#]}" ; do
(( count[$c]++ ))
if (( count[$c] > max )) ; then
max=${count[$c]}
idx=$c
fi
done
echo $idx

Related

Bash iterating of each variable and adding number

I am processing a 7 PCAP file by splitting the file based on MAC address this is fine but I have various variables where I want to iterate through
${macs[*]} - I have a list 10 of different MAC address I would like to iterate through them
${devices[0]} - I have a list of 10 devices e.g. Samsung, Phillips I want to add a number to each file
for pcf in $pcap_file
do
for mac in ${macs[*]}
do
echo "$mac" >&2
/usr/bin/tshark -r "$pcf" -Y "eth.addr eq $mac" -w ${devices[0]}.pcap
done
done
At the moment I am manually uncommenting/commenting them
macs=( d0:45:a8:00:67:5e )
macs=( 44:65:0d:56:cc:d3 )
macs=( 70:ee:50:34:34:43 )
devices=('Samsunghub_1' 'Samsunghub_2' 'Samsunghub_3' 'Samsunghub_4' 'Samsunghub_5' 'Samsunghub_6' 'Samsunghub_7')
devices=('Echo_1' 'Echo_2' 'Echo_3' 'Echo_4' 'Echo_5' 'Echo_6' 'Echo_7')
devices=('netamo_1' 'netamo_2' 'netamo_3' 'netamo_4' 'netamo_5' 'netamo_6' 'netamo_7')
I want to iterate through each PCAP file extract based on the MAC address then label each one based on the "devices" but adding a number at the end
I'm not entirely clear what you are doing based on your post, but if you are making counts of the items it may be more useful to structure the devices as an associative array. Then just increment the value as you increase your count.
declare -A AA_devices
AA_devices[Samsunghub]="7"
AA_devices[Echo]="7"
AA_devices[netamo]="7"
You'd presumably want to set each equal to zero to begin. Once incremented you could use that data to either create the arrays you have outlined above (by iterating over your associative array) or whatever you want.
If you are counting which device based on which MAC address, you could then set an if/then statement that increments the devices counts.
for mac in "${macs[#]}" ; do
if mac = xx:xx:xx:xx ; then
AA_devices[netamo]+=1
Otherwise, let me know where I've misunderstood and I'll try again!

how to display 3 random pictures out of 8 using bash

I have 8 images in a directory.
the path is /blabla.com/img.
I need to access this path and choose 3 out of 8 randomly and display those.
If 3 pics are the same, it should echo "yeeey".
Otherwise, "neeey" and record these responses in a text file.
I am not going to do your homework for you!
However I can give you some insight:
store your 8 file names in an array
call $RANDOM % 8 3 times and store the value in 3 index variables
use the 3 index variables to extract your 3 files
use sha256sum, sha512sum or md5sum to compute the signature of your images and store the result in 3 variables
compare the values of the 3 variables if they are the same echo "yeeey" else echo "neeey"
if on top of that you want to display the picture as written in your post you could call eog or other similar tool with the finename as parameter and of course in background, with a & at the end of the command call.
Good luck with your assignment and let me know if you need help!
let's array an array of distinct elements (for example 8):
array=({A..H})
(1) use RANDOM special variable modulo the number of elements to get a random number between 0 and number-1 inclusive
number=$((RANDOM%${#array[#]}))
the first random element is
first=${array[number]}
remove the element from array and reassign the array to reindex without gap (declare -p array to see)
unset array[number]
array=("${array[#]}")
restart from (1)

Checking if a word is already in a list (Bash)

I'm writing a small bash script and am trying to test is a newly generated word is already in a list of all previously made words.
This is what I'm working with now:
dict=("word1"... "word21") #there would be 21 words in here
prev_guesses=()
guess_creator() {
guess=""
for i in {1..5} ;
do
guess_num=$( shuf -i 0-21 -n 1 )
guess+="${dict[$guess_num]}"
done
# using recursion to take another guess
if [ $guess (is in) $prev_guesses] ; then
guess_creator
else
prev_guess+=($guess)
fi
}
I'm also not sure if recursion works like this in bash. If it doesn't, I'm asking here how to actually "unbreak" this code.The idea is to have this function constantly outputting a unique string every time it runs so I can use it later on in the script.
I have three questions:
How can I compare guess to the list prev_guesses and get a true or false output
How can I append guessed string to the list prev_guesses (I just checked it and it is just concatenating the strings together, I need a list like prev_guesses=("guess1" "guess2"...) - I may have solved this with the final edit.
Does this recursion in guess_creator work?
Associative Arrays
Since you are only interested in »is this word in the list or not?« but not in the order of entries, you could use an associative array (also known as dictionary or hash map) to store your words. Checking whether an entry is in such a map is very fast (time complexity O(1)):
declare -A oldGuesses=([word1]= [word2]= [word3]=)
if [[ "${oldGuesses[$guess]+1}" ]]; then
echo "$guess was already taken"
else
echo "$guess was not taken yet"
fi
You can add an entry to dict using
dict["newEntry"]=
Don't worry about the empty right hand side. Maps are normally used to store key-value pairs. Here we only use the keys (the things which are written inside the []).
Avoiding the list of guesses completely
You mentioned that you want to bruteforce and that the list could grow up to 4M entries. I would advise against using bash, but even more against storing all guesses at all (no matter what language you are using).
Instead, enumerate all possible guesses in an ordered way:
You want to create guesses which are five concatenated words?
Just create five for-loops:
for w1 in "${dict[#]}"; do
for w2 in "${dict[#]}"; do
for w3 in "${dict[#]}"; do
for w4 in "${dict[#]}"; do
for w5 in "${dict[#]}"; do
guess="$w1$w2$w3$w4$w5"
# do something with your guess here
done
done
done
done
done
Benefits of this approach over your old approach:
Don't have to store 4M guesses.
Don't have to search through 4M guesses whenever taking a new guess.
Guarantees that the same guess is not picked over and over again.
Terminates when all possible guesses are made.
There's nothing like that in bash for arrays (Socowi's idea of using Associative Array is better), you would have to iterate through the list again, or maybe try to use grep or something
to refer to all the elements of an array you need the syntax ${prev_guesses[*]}
so you can concatenate with something like
prev_guesses=(${prev_guesses[*]} $guess)
Spaces in your words would make it all more complicated
It should do. BUT....
That's the hard way. If you want to avoid repeating guesses, better to take out each guess from the array when you take it, so you can't take it again.
Easier still is to use the shuf commmand to do everything
guess=($( shuf -e ${dict[*]} -n 5))
shuffle your words and take the first five

Bash history enumerated from the end

In bash one can !-1 to execute the 1 command from the history enumerated from the end starting at 0. But what how can one view history so that mentioned enumeration is shown instead of usual one by built-in means? Of course one can write a simple script for that but I feel like there should be some kind of option for that.
Another question is if there is a way to somehow switch history expansion so that - sign would be unnessasary. Say exchanging meaning of !1 and !-1.
Showing negative indices is simple to implement. Just take the length of history (or get it from history 1), and subtract it from the index of all other history items.
neghistory() {
local i n s
read n s < <(history 1)
history "$#" | while read i s; do
printf '%5d %s\n' $((i-n-1)) "$s"
done
}
I don't see any built-in ways to affect history's output like this nor change how indices in history expansion works.

Automatic update data file and display?

I am trying to develop a system where this application allows user to book ticket seat. I am trying to implement an automatic system(a function) where the app can choose the best seats for the user.
My current database(seats.txt) file store in this way(not sure if its a good format:
X000X
00000
0XXX0
where X means the seat is occupied, 0 means nothing.
After user login to my system, and choose the "Choose best for you", the user will be prompt to enter how many seats he/she want (I have done this part), now, if user enter: 2, I will check from first row, see if there is any empty seats, if yes, then I assign(this is a simple way, once I get this work, I will write a better "automatic-booking" algorithm)
I try to play with sed, awk, grep.. but it just cant work (I am new to bash programming, just learning bash 3 days ago).
Anyone can help?
FYI: The seats.txt format doesn't have to be that way. It can also be, store all seats in 1 row, like: X000X0XXX00XXX
Thanks =)
Here's something to get you started. This script reads in each seat from your file and displays if it's taken or empty, keeping track of the row and column number all the while.
#!/bin/bash
let ROW=1
let COL=1
# Read one character at a time into the variable $SEAT.
while read -n 1 SEAT; do
# Check if $SEAT is an X, 0, or other.
case "$SEAT" in
# Taken.
X) echo "Row $ROW, col $COL is taken"
let COL++
;;
# Empty.
0)
echo "Row $ROW, col $COL is EMPTY"
let COL++
;;
# Must be a new line ('\n').
*) let ROW++
let COL=1
;;
esac
done < seats.txt
Notice that we feed in seats.txt at the end of the script, not at the beginning. It's weird, but that's UNIX for ya. Curiously, the entire while loop behaves like one big command:
while read -n 1 SEAT; do {stuff}; done < seats.txt
The < at the end feeds in seats.txt to the loop as a whole, and specifically to the read command.
It's not really clear what help you're asking for here. "Anyone can help?" is a very broad question.
If you're asking if you're using the right tools then yes, the text processing tools (sed/awk/grep et al) are ideal for this given the initial requirement that it be done in bash in the first place. I'd personally choose a different baseline than bash but, if that's what you've decided, then your tool selection is okay.
I should mention that bash itself can do a lot of the things you'll probably be doing with the text processing tools and without the expense of starting up external processes. But, since you're using bash, I'm going to assume that performance is not your primary concern (don't get me wrong, bash will probably be fast enough for your purposes).
I would probably stick with the multi-line data representation for two reasons. The first is that simple text searches for two seats together will be easier if you keep the rows separate from each other. Otherwise, in the 5seat-by-2row XXXX00XXXX, a simplistic search would consider those two 0 seats together despite the fact they're nowhere near each other:
XXXX0
0XXXX
Secondly, some people consider the row to be very important. I won't sit in the first five rows at the local cinema simply because I have to keep moving my head to see all the action.
By way of example, you can get the front-most row with two consecutive seats with (commands are split for readability):
pax> cat seats.txt
X000X
00000
0XXX0
pax> expr $(
(echo '00000';cat seats.txt)
| grep -n 00
| tail -1
| sed 's/:.*//'
) - 1
2
The expr magic and extra echo are to ensure you get back 0 if no seats are available. And you can get the first position in that row with:
pax> cat seats.txt
| grep 00
| tail -1
| awk '{print index($0,"00")}'
3

Resources