Sorting an array in lexicographical order. Pure bash - arrays

I have two arrays with IPs. I want to combine them into a third array and apply an ascending ordering.
#!/bin/bash
#
dbip[0]=1.1.1.1
dbip[1]=1.1.1.2
dbip[2]=1.1.1.3
dbip[3]=1.1.1.4
dbip[4]=1.1.1.5
dbip[5]=1.1.1.10
dbip[6]=1.1.1.9
ngip[0]=1.1.1.5
ngip[1]=1.1.1.6
ngip[2]=1.1.1.7
ngip[3]=1.1.1.1
ngip[4]=1.1.1.11
#I am adding the dbip array into the final one
for (( i=0; i<${#dbip[#]}; i++ ))
do
allip[$i]=${dbip[$i]}
done
#Remembering the no. of elements in the final array
var=${#allip[#]}
echo "$var"
#Adding the ngip array into the final one
for (( i=0; i<${#ngip[#]}; i++ ))
do
allip[$var+$i]=${ngip[$i]}
done
#Printing the initial order of the elements in the array
echo "size= ${#allip[#]}"
for (( i=0; i<${#allip[#]}; i++ ))
do
echo "${allip[$i]}"
done
#Sorting the array in ascending order
for (( i=0; i<${#allip[#]}; i++ ))
do
for (( j=$i; j<${#allip[#]}; j++ ))
do
if [ allip[$i] \> allip[$j] ];
then
aux=${allip[$i]}
allip[$i]=${allip[$j]}
allip[$j]=$aux;
fi
done
done
echo "###############################"
#Printing the final form of the array
for (( i=0; i<${#allip[#]}; i++ ))
do
echo "${allip[$i]}"
done
Problem is that the output is not ordered in numerical or lexicographical way.
Output:
1.1.1.1
1.1.1.11
1.1.1.1
1.1.1.2
1.1.1.3
1.1.1.4
1.1.1.5
1.1.1.10
1.1.1.9
1.1.1.5
1.1.1.7
1.1.1.6
The output should be like this:
1.1.1.1
1.1.1.1
1.1.1.2
1.1.1.3
.......
1.1.1.10
1.1.1.11
or like this
1.1.1.1
1.1.1.1
1.1.1.10
1.1.1.11
so i can remove the duplicates later.
How can i do this in a pure programming way in bash. No pipes.
Note that IPs can be from different classes: 10.55.72.190, 10.55.70.1, 10.51.72.44, etc.

Here is "pure" bash:
dbip=( [0]=1.1.1.1 [1]=1.1.1.2 [2]=1.1.1.3 [3]=1.1.1.4 [4]=1.1.1.5 [5]=1.1.1.10 [6]=1.1.1.9 )
ngip=( [0]=1.1.1.5 [1]=1.1.1.6 [2]=1.1.1.7 [3]=1.1.1.1 [4]=1.1.1.11 )
allip=( $(printf "%s\n" "${dbip[#]}" "${ngip[#]}" | sort -V) )
printf "%s\n" "${allip[#]}"
1.1.1.1
1.1.1.1
1.1.1.2
1.1.1.3
1.1.1.4
1.1.1.5
1.1.1.5
1.1.1.6
1.1.1.7
1.1.1.9
1.1.1.10
1.1.1.11

A pure bash implementation (no call to any tool like sort).
The idea: convert IPs to integers, perform a bubble sort on these, convert back to x.x.x.x format.
#! /bin/bash
ips=(192.0.2.235 1.1.1.1 1.1.1.11 1.1.1.1 1.1.1.2 1.1.1.3 1.1.1.4
1.1.1.5 1.1.1.10 1.1.1.9 1.1.1.5 1.1.1.7 1.1.1.6)
# integer IPs
intips=()
echo Initial addresses:
for ((i=0; i<${#ips[#]}; ++i)); do
echo ${ips[$i]}
done
echo
# convert ip to int
ip2int() {
local -i intip
local -a ip
local IFS="."
ip=($1)
intip=$(( ${ip[0]}*2**24 + ${ip[1]}*2**16 + ${ip[2]}*2**8 + ${ip[3]} ))
echo $intip
}
# convert int to ip
int2ip() {
local -i a1 a2 a3 a4 intip=$1
a1=$(( intip / (2**24) )); ((intip -= a1*2**24))
a2=$(( intip / (2**16) )); ((intip -= a2*2**16))
a3=$(( intip / (2**8) )); ((intip -= a3*2**8))
a4=$(($intip))
echo "$a1.$a2.$a3.$a4"
}
# simple bubble sort (ascending) of an integer array
sortn_asc() {
local -a array=( "$#" )
local -i max i val1 val2
for (( max=$(( ${#array[#]} - 1 )); max > 0; max-- )); do
for (( i=0; i<max; i++ )); do
val1=${array[$i]}
val2=${array[$((i + 1))]}
# switch if necessary
if (( $val1 > $val2 )); then
local tmp=$val1
array[$i]=$val2
array[$(($i + 1))]=$tmp
fi
done
done
echo "${array[#]}"
}
# convert ips to integers
for ((i=0; i<${#ips[#]}; ++i)); do
intips[$i]=$(ip2int ${ips[$i]})
done
# sort
intips=( $(sortn_asc ${intips[#]}) )
# convert and echo sorted integers table
echo Sorted addresses:
for ((i=0; i<${#intips[#]}; ++i)); do
echo $(int2ip ${intips[$i]})
done
exit 0
The output:
br#lorien:~/tmp$ ./19398658.sh
Initial addresses:
192.0.2.235
1.1.1.1
1.1.1.11
1.1.1.1
1.1.1.2
1.1.1.3
1.1.1.4
1.1.1.5
1.1.1.10
1.1.1.9
1.1.1.5
1.1.1.7
1.1.1.6
Sorted addresses:
1.1.1.1
1.1.1.1
1.1.1.2
1.1.1.3
1.1.1.4
1.1.1.5
1.1.1.5
1.1.1.6
1.1.1.7
1.1.1.9
1.1.1.10
1.1.1.11
192.0.2.235

Related

Compare each element in one array which contains all Elements from another Array

I have 2 array like below :
Array 1 :
3.3.3.3
2.2.2.2
1.1.1.1
3.3.3.3
1.1.1.5
1.1.1.3
1.1.1.4
1.1.1.3,1.1.1.1,2.2.2.2
2.2.2.2,1.1.1.1,1.1.1.3
Array 2:
1.1.1.3
2.2.2.2
1.1.1.1
As you can see Array1[6] and Array1[7] contains all the values from Array 2 .
My code in Powershell:
$siIPs = $sourceIPsource.IpAddress
if ($siIPs.Count -gt 1)
{
$siIPs= $siIPs -join ','
}
if($initialuniversalipsets.value -contains $siIPs)
{
$index=$initialuniversalipsets.value.IndexOf($siIPs) Write-host $index
}
Below are array value:
H:\Modules> $initialuniversalipsets.value 3.3.3.3 2.2.2.2,1.1.1.1 3.3.3.3 1.1.1.5 1.1.1.3 1.1.1.4 1.1.1.3,1.1.1.1,2.2.2.2 1.1.1.3,1.1.1.1,2.2.2.2
H:\Modules> $tg 1.1.1.3 2.2.2.2 1.1.1.1
How can I get the first element in first array contains all the values from second array ordered?

Fetching data into an array

I have a file like this below:
-bash-4.2$ cat a1.txt
0 10.95.187.87 5444 up 0.333333 primary 0 false 0
1 10.95.187.88 5444 up 0.333333 standby 1 true 0
2 10.95.187.89 5444 up 0.333333 standby 0 false 0
I want to fetch the data from the above file into a 2D array.
Can you please help me with a suitable way to put into an array.
Also post putting we need put a condition to check whether the value in the 4th column is UP or DOWN. If it's UP then OK, if its down then below command needs to be executed.
-bash-4.2$ pcp_attach_node -w -U pcpuser -h localhost -p 9898 0
(The value at the end is getting fetched from the 1st column.
You could try something like that:
while read -r line; do
declare -a array=( $line ) # use IFS
echo "${array[0]}"
echo "${array[1]}" # and so on
if [[ "$array[3]" ]]; then
echo execute command...
fi
done < a1.txt
Or:
while read -r -a array; do
if [[ "$array[3]" ]]; then
echo execute command...
fi
done < a1.txt
This works only if field are space separated (any kind of space).
You could probably mix that with regexp if you need more precise control of the format.
Firstly, I don't think you can have 2D arrays in bash. But you can however store lines into a 1-D array.
Here is a script ,parse1a.sh, to demonstrate emulation of 2D arrays for the type of data you included:
#!/bin/bash
function get_element () {
line=${ARRAY[$1]}
echo $line | awk "{print \$$(($2+1))}" #+1 since awk is one-based
}
function set_element () {
line=${ARRAY[$1]}
declare -a SUBARRAY=($line)
SUBARRAY[$(($2))]=$3
ARRAY[$1]="${SUBARRAY[#]}"
}
ARRAY=()
while IFS='' read -r line || [[ -n "$line" ]]; do
#echo $line
ARRAY+=("$line")
done < "$1"
echo "Full array contents printout:"
printf "%s\n" "${ARRAY[#]}" # Full array contents printout.
for line in "${ARRAY[#]}"; do
#echo $line
if [ "$(echo $line | awk '{print $4}')" == "down" ]; then
echo "Replace this with what to do for down"
else
echo "...and any action for up - if required"
fi
done
echo "Element access of [2,3]:"
echo "get_element 2 3 : "
get_element 2 3
echo "set_element 2 3 left: "
set_element 2 3 left
echo "get_element 2 3 : "
get_element 2 3
echo "Full array contents printout:"
printf "%s\n" "${ARRAY[#]}" # Full array contents printout.
It can be executed by:
./parsea1 a1.txt
Hope this is close to what you are looking for. Note that this code will loose all indenting spaces during manipulation, but a formatted update of the lines could solve that.

Format output of array elements with variable lenght

I have this arrays, with output to a file.txt:
the first array contains all IPs in subnet (192.168.1.0, 192.168.1.1, ecc)
the second array contains an 1 if that host is up, a 0 if down, in order to obtain a matrix like this:
192.168.1.0 192.168.1.1 ...
0 1 ...
How can I allign this elements? I want that the bit status (0 or 1) is at the center of the IP address.
Consider that the IP address may vary, depending on the machine, for example it can be
71.5.0.0
I put all my script if you need more info:
#!/bin/bash
# written by Cosimo Colaci
# Declarations
ROOT_UID=0
E_NOROOT=65
declare -a iplist
echo "Reading hosts status..."
# Must run as root.
# if [ "$UID" -ne "$ROOT_UID" ]
# then
# echo "You need to run this script as root. Exiting..."
# exit $E_NOROOT
# fi
# Find Gateway IP and Netmask
ip_gateway=$(route | tail -1 | awk '{print $1}')
netmask=$(route | tail -1 | awk '{print $3}')
# echo "$netmask" FOR DEBUG
# Find subnet
OLDIFS="$IFS"
IFS=.
read -r i1 i2 i3 i4 <<< "$ip_gateway"
read -r m1 m2 m3 m4 <<< "$netmask"
subnet_ip=$(printf "%d.%d.%d.%d\n" "$((i1 & m1))" "$((i2 & m2))" "$((i3 & m3))" "$((ii4 & m4))")
# echo "$subnet_ip" FOR DEBUG
IFS="$OLDIFS"
# Express netmask in CIDR notation, to know how many hosts to scan
cidr_by_netmask() {
#function returns prefix for given netmask in arg1
bits=0
for octet in $(echo $1| sed 's/\./ /g'); do
binbits=$(echo "obase=2; ibase=10; ${octet}"| bc | sed 's/0//g')
let bits+=${#binbits}
done
echo "/${bits}"
}
suffix=$(cidr_by_netmask $netmask)
# echo "$suffix" FOR DEBUG
# First raw of matrix file: all IPs in subnet
iplist=( $(nmap -sL $subnet_ip$suffix | grep "Nmap scan report" | awk '{print $NF}') )
# echo "${iplist[#]}" FOR DEBUG
let i=1
let j=0
while [ $j -lt ${#iplist[#]} ]
do
raw1[$i]=${iplist[$j]}
((i++))
((j++))
done
raw1[0]="#IP/TIME"
for value in ${raw1[#]}
do
echo -en "$value\t"
done >ip-time_matrix.txt
echo -e "\n" >>ip-time_matrix.txt
# Other raws
let j=1
let k=0
export j
export k
echo "Scanning hosts every 10s..."
echo "Hit CTRL-C to stop and check file \"ip-time_matrix.txt\""
while true
do
nmap -sP $subnet_ip$suffix &>temp_esame95a.txt
raw2[0]="$(date +%T)"
for ip in ${iplist[#]}
do
if grep "$ip" temp_esame95a.txt &>/dev/null
then
raw2[$j]="---1---"
else
raw2[$j]="---0---"
fi
((j++))
done
for value in ${raw2[#]}
do
echo -en "$value\t"
done >>ip-time_matrix.txt
echo -e "\n" >>ip-time_matrix.txt
sleep 10
j=1
done
In a comment a suggested printf "%15.15s %s" "${ip}" ${bitstatus}. I should have added a \n, printf "%15.15s %s\n" "${ip}" ${bitstatus}.
OP reacted, that it solved his problem.
For the arrays:
ips=(192.168.1.0 127.001.0.1 192.168.1.1 192.169.1.1)
raw=(1 1 0 1)
you could use printf and cut commands,
printf "%15s" "${ips[#]}" | cut -c5-
printf "%15s" "${raw[#]}" | cut -c5-
to align the output:
192.168.1.0 127.001.0.1 192.168.1.1 192.169.1.1
1 1 0 1

Bash: Iterating through an array to get the value at each index

The main goal of this program is to simulate the drawing of a card as many times as the user chooses and then print out a histogram using '*' to represent the number of hits on each card. However, the problem that I am having is retrieving the elements in each array and printing the stars that correlates with them. This is what I have so far:
timelimit=5
echo -e "How many trials would you like to run? \c"
read -t $timelimit trials
if [ ! -z "$trials" ]
then
echo -e "\nWe will now run $trials trials"
else
trials=10
echo -e "\nWe will now run the default amount of trials: $trials"
fi
count=1
MAXCARD=53
declare -a CARDARRAY
while [ "$count" -le $trials ]
do
card=$RANDOM
let "card %= MAXCARD"
let "CARDARRAY[$card] += 1"
let "count += 1"
done
echo ${CARDARRAY[#]}
for (( i=0; i<${#CARDARRAY[#]}; i++));
do
#declare "temp"="${CARDARRAY[$i]}"
#echo "$temp"
#for (( j=0; j<temp; j++));
#do
#echo "*"
#done
echo "$i"
done
Obviously the last for loop is where I'm having trouble and is currently the latest attempt at printing the stars according to how many hits each card has.
You were pretty close. Here's how I'd paraphrase your script:
#!/bin/bash
timelimit=5
printf %s 'How many trials would you like to run? '
read -t $timelimit trials
if [[ ! -z $trials ]] ; then
printf '\nWe will now run %d trials\n' $trials
else
trials=10
printf '\nWe will now run the default amount of trials: %d\n' $trials
fi
count=1
MAXCARD=53
declare -a CARDARRAY
while (( trials-- )) ; do
(( CARDARRAY[RANDOM % MAXCARD] += 1 ))
done
printf '%s\n' "${CARDARRAY[*]}"
for (( i=0 ; i<MAXCARD ; i++ )) ; do
printf %02d: $i
for (( j=0 ; j<${CARDARRAY[i]:-0} ; j++ )) ; do
printf %s '*'
done
printf '\n' ''
done
You can use set -xv to see what bash is running at each step.

grep result into array

I want to count the number of IP's in a given file using this function, the IP's should go into an array so that I can use them later, but I get 'declare: not found' and 'cnt+=1: not found', why is this?
#!/bin/bash
searchString=$1
file=$2
countLines()
{
declare -a ipCount
cnt=0
while read line ; do
((cnt+=1))
ipaddr=$( echo "$line" | grep -o -E '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' )
((ipCount[$ipaddr]+=1))
done
for ip in ${ipCount[*]}
do
printf "%-15s %s\n" "$ip" "${ipCount[$ip]}"
done
echo "total count=$cnt"
}
if [ $# -lt 2 ]; then
echo "missing searchString and file"
else
grep "$searchString" $file | countLines
fi
This is a piece of the test file I am trying on
Apr 25 11:33:21 Admin CRON[2792]: pam_unix(cron:session): session opened for user 192.168.1.2 by (uid=0)
Apr 25 12:39:01 Admin CRON[2792]: pam_unix(cron:session): session closed for user 192.168.1.2
Apr 27 07:42:07 John CRON[2792]: pam_unix(cron:session): session opened for user 192.168.2.22 by (uid=0)
The desired output would be just the IP's inside an array, then also a 'count' on how many IP's there were.
I know I can get the ip's with a grep command but I would like to do more with it later, and it's important that it's in an array.
Your two main issues were that you were using declare -a but to declare an associative array, you need declare -A. Then, to iterate over the keys of an associative array, you need to use for foo in ${!ArrayName[#]}. I also added some quotes to your variables to be on the safe side:
#!/bin/bash
searchString="$1"
file="$2"
countLines()
{
## You need -A for associative arrays
declare -A ipCount
cnt=0
while read line ; do
(( cnt+=1 ))
ipaddr=$( echo "$line" | grep -o -E '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' )
(( ipCount["$ipaddr"]+=1 ))
done
## To iterate over the keys of an associative
## array, you need ${!ArrayName[#]}
for ip in "${!ipCount[#]}"
do
printf "%-15s %s\n" "$ip" "${ipCount[$ip]}"
done
echo "total count=$cnt"
}
if [ $# -lt 2 ]; then
echo "missing searchString and file"
else
grep "$searchString" "$file" | countLines
fi
This is the output of the above on your example file:
$ bash a.sh 27 a
192.168.2.22 1
192.168.1.2 2
total count=3

Resources