LinuxQuestions.org
Review your favorite Linux distribution.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Linux Forums > Linux - Newbie
User Name
Password
Linux - Newbie This Linux forum is for members that are new to Linux.
Just starting out and have a question? If it is not in the man pages or the how-to's this is the place!

Notices


Reply
  Search this Thread
Old 12-29-2011, 06:30 PM   #1
chucklesp9
LQ Newbie
 
Registered: Dec 2011
Posts: 7

Rep: Reputation: Disabled
Char Array Comparison


I am trying to compare two arrays against each other and get the differences between them written to another array. Here is what I have so far:

Code:
all_name=(M1 M2 M3 M4 M5 M6 M7 M8 M9 M10)
name=(M1 M2 M3 M8)

for x in ${!all_name[*]}; do
	for y in ${!name[*]}; do
		new_name=( ${!name[*]} - ${!all_name[*]}
	done
done

echo ${new_name[0]}
I have tried to look at other posts in LinuxQuestions.org forum and get a better answer but have not found one that works for me yet.

I need to be able to get the first difference between then from the all_name array. I can either have it written to another array or to a character variable.
 
Old 12-30-2011, 10:40 AM   #2
David the H.
Bash Guru
 
Registered: Jun 2004
Location: Osaka, Japan
Distribution: Arch + Xfce
Posts: 6,852

Rep: Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037
Please define your needs a little more. What exactly do you mean by differences? Is it the first position in each array that has a non-matching element? The first item in one array that's not contained in the other? Or something else?

Second, what is your actual problem? Is there anything wrong with the current code? Or are you just asking if there's a better solution?

To start with a few observations though:

1) When you use the "[*]" index, you output the whole array as a single string. Then since you haven't quoted any of them, the shell does re-splits them on whitespace into separate words again. That's why there's also "[@]", which outputs the whole array as a list of individual items, that when quoted act as if they are individually quoted.

So always use "[@]", with quotes, unless you have a special need to do otherwise.

2) The exclamation point in pattern "${!array[*]}" (or ${!array[@]}, with the same behavior as #1 above) spits out a list of the array's index numbers. This means the loop variable will contain only a number, not the value. You probably do not want this.

3) This line:
Code:
new_name=( ${!name[*]} - ${!all_name[*]}
#1 and #2 apply, of course. It's also missing the closing bracket. But most importantly, you can't just "subtract" one array from another. So all this does is set the new array to all the index numbers of the first array (after word-splitting), then a hyphen, and then all the indexes of the second array. And it does it over and over for each of the double-loop iterations.

What you really want to do is run a test of some kind to compare the strings contained in both array elements, and only print out the ones you want.

So perhaps something like this would be more appropriate.

Code:
all_name=( M1 M2 M3 M4 M5 M6 M7 M8 M9 M10 )
name=( M1 M2 M3 M8 )

for x in "${all_name[@]}"; do
	[[ "${name[*]}" != *$x* ]] && new_name+=( "$x" )
done

echo "${new_name[@]}"
Note though that this is just a quick example. For one thing it depends on testing each individual element in all_name against the full string of name. If a single name entry ever contained as a substring the whole of an entry in all_name, it would produce a false negative.

Last edited by David the H.; 12-30-2011 at 11:05 AM. Reason: cleanup on aisle 3
 
Old 12-30-2011, 10:59 AM   #3
David the H.
Bash Guru
 
Registered: Jun 2004
Location: Osaka, Japan
Distribution: Arch + Xfce
Posts: 6,852

Rep: Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037
Here's a safer version of the above. It tests each array element separately, so there's no danger of matching substrings.

Code:
all_name=( M{1..10} )
name=( M{1,2,3,8} )

for x in "${all_name[@]}"; do
	for y in "${name[@]}"; do
		[[ "$y" == "$x" ]] && (( count++ ))
	done

	(( ! count )) && new_name+=( "$x" )
	unset count
done

echo "${new_name[@]}"
The new_name array still simply contains all the entries in all_name that don't exist in name however, in order found.

Notice also how you can simplify the generation of sequences with brace expansion.

Last edited by David the H.; 12-30-2011 at 11:05 AM. Reason: cleanup again
 
Old 12-30-2011, 06:02 PM   #4
chucklesp9
LQ Newbie
 
Registered: Dec 2011
Posts: 7

Original Poster
Rep: Reputation: Disabled
The problem that I am trying to solve is a check against all possible running systems compared to the ones that are actually running. This is a back end systems check script that I have been asked to write. I am a software tester that is subcontracted out to a client that runs a large part of their back end with Linux.

Here is an example of what I need to be able to achieve with the array comparison.

Code:
all_name=(M1 M2 M3 M4 M5 M6 M7 M8 M9 M10)
name=(M1 M2 M3 M8)

new_name=(M4 M5 M6 M7 M9 M10)
As "name" changes I need the output to change to reflect the changes.

I tried the code that you gave in the second post and it worked until systems went offline then come back up. As soon as the "name" array changed the changes were no longer reflected in the output results.

As per the example above your code works great but if the following happens it breaks

Code:
all_name=(M1 M2 M3 M4 M5 M6 M7 M8 M9 M10)
name=(M1 M2 M4 M8 M9)

new_name=(M1 M2 M3 M4 M5 M6 M7 M9 M10)
This is an exact copy of the "new_name" array that the code gives.

Thank you for your help so far.
 
Old 12-30-2011, 06:38 PM   #5
Cedrik
Senior Member
 
Registered: Jul 2004
Distribution: Slackware
Posts: 2,140

Rep: Reputation: 244Reputation: 244Reputation: 244
You could use associative array if you use bash version > 4
(bash --version)

Basically use arrays elements as keys and let the associated value be incremented each time
Code:
#!/bin/bash

all_name=(M1 M2 M3 M4 M5 M6 M7 M8 M9 M10)
name=(M1 M2 M4 M8 M9)

# concatenate both arrays
all=("${all_name[@]}" "${name[@]}")

# declare an associative array
declare -A tmp

# increment the values
for i in "${all[@]}"
do
    let tmp[$i]++
done

# select the values equal to 1 (those who have been incremented once)
for k in "${!tmp[@]}"
do
    [[ "${tmp[$k]}" = 1 ]] && new_name+=( "$k" )
done

echo "${new_name[@]}"
Or using sed:

Code:
all_name=( M1 M2 M3 M4 M5 M6 M7 M8 M9 M10 )
name=( M1 M2 M3 M8 )

new_name=${all_name[@]}

for s in ${name[@]}
do
    new_name=$(echo $new_name | sed -e "s/$s//")
done

echo $new_name

Last edited by Cedrik; 12-31-2011 at 06:37 AM.
 
1 members found this post helpful.
Old 12-31-2011, 08:42 AM   #6
David the H.
Bash Guru
 
Registered: Jun 2004
Location: Osaka, Japan
Distribution: Arch + Xfce
Posts: 6,852

Rep: Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037
Quote:
I tried the code that you gave in the second post and it worked until systems went offline then come back up. As soon as the "name" array changed the changes were no longer reflected in the output results.
That doesn't sound like a problem with the testing code itself; it sounds more like you just haven't ensured that the results are properly re-evaluated every time the name array changes.

Perhaps you just haven't noticed that new_name is set using "+=", which only adds new entries to an existing list. So you have to unset the existing array before every re-evaluation.

I'd probably set it up as a function if it's something that needs to be done often.

Code:
servermatch(){
	unset new_name
	local all_name=( M{1..10} )

	for x in "${all_name[@]}"; do
		for y in "$@"; do
		[[ "$y" == "$x" ]] && (( count++ ))
		done

		(( ! count )) && new_name+=( "$x" )
		unset count
	done
}

name=( M1 M2 M3 M8 )	

servermatch "${name[@]}"

echo "${new_name[@]}"
Then just run the servermatch function with the new name values whenever you need it in the script. Or perhaps even simplify the function so that it only tests a single value against the all_name array, and call on it separately for each name value as you need it.


But as Cedrik points out, associative arrays are even better for this kind of thing. You might even want to think about redesigning the whole logic of the script around them.

Code:
#!/bin/bash

declare -A servers

servers=(
	[M1]=down [M1]=down [M3]=down [M4]=down	[M5]=down
)

for name in "${!servers[@]}"; do

	check_server_status_command "$name"

	case $? in
		0) 	servers[$name]=up
			upservers+=( "$name" )	;;

		*)	servers[$name]=down
			dnservers+=( "$name" )	;;
	esac

done

echo "These servers are up:"
printf "\t%s\n"	"${upservers[@]}"

echo "These servers are down:"
printf "\t%s\n"	"${dnservers[@]}"


name=( M1 M3 M7 M8 )
for server in "${name[@]}"; do
	echo "Current status for $server is ${server[$name]}"
done
...Or whatever action you want to apply, of course. And again, if you use something like the above, be sure to unset upservers and dnservers between each use, as they are set with the "+=" pattern.


This page has a good section on associative arrays and other techniques you can use for this kind of operation:

http://mywiki.wooledge.org/BashFAQ/006

Last edited by David the H.; 12-31-2011 at 09:05 AM. Reason: some updated info & corrections
 
Old 12-31-2011, 11:31 AM   #7
chucklesp9
LQ Newbie
 
Registered: Dec 2011
Posts: 7

Original Poster
Rep: Reputation: Disabled
Cedrik that worked perfectly. Thank you both for all the help.
 
  


Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
[C++] comparison vector and array of char robertodb Programming 3 09-20-2011 06:06 AM
How do I point a char pointer to a part of another char array? trist007 Programming 8 11-06-2010 07:56 PM
scanf reading newline into char array while reading 1 char at a time austinium Programming 6 09-26-2010 11:27 PM
How to convert short array to char array? bvkim Programming 4 06-08-2010 09:26 AM
C++ help Dynamic array and "invalid conversion from ‘char’ to ‘char*’" heathf Programming 2 04-25-2009 09:20 PM

LinuxQuestions.org > Forums > Linux Forums > Linux - Newbie

All times are GMT -5. The time now is 10:11 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration