LinuxQuestions.org
Help answer threads with 0 replies.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Linux Forums > Linux - General
User Name
Password
Linux - General This Linux forum is for general Linux questions and discussion.
If it is Linux Related and doesn't seem to fit in any other forum then this is the place.

Notices


Reply
  Search this Thread
Old 11-17-2011, 08:52 AM   #16
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

Heh, no need to feel particularly honored. I'm no Stallman or Torvalds. I'm just an anonymous, amateur scripting hack, trying to help out as best as I can.


Interestingly, I came up with a similar printf solution myself too. I ultimately decided against it precisely because of the word-splitting weakness. The array technique is still better, IMO, because you never have to expose the data string itself to shell parsing, whereas the printf solution actually depends on it.

Last edited by David the H.; 11-17-2011 at 08:55 AM. Reason: removed unnecessary comment
 
Old 11-18-2011, 02:42 PM   #17
nano2
Member
 
Registered: May 2007
Posts: 100

Original Poster
Rep: Reputation: 15
I am trying to pass in the following arrays in to the fn2 but it gives me atotal of both arrays
Am I passing them in correctly ?

Code:
#!/bin/bash
fn2 ()
{

array1=( $@ )
array2=( $@ )
cnt=${#arr[@]}
echo "the count is " $cnt
for (( idx = 0 ; idx < cnt ; idx++ ))
do
  echo ${array2[idx]}
  echo ${array1[idx]}
  echo "the count ifor array2 "${#arr[@]} 
done

#############array1=(dog cat lion cow)
#######array2=(lion rat mouse dog zebra pet cat bird cow calf sheep)
list2="${array2[*]}"
cnt=${#array1[@]}
for ((i=0; i <cnt ; i++ )); do
  pref=${array1[i]}
  if grep -wq $pref <<< "$list2"; then
      preflist2+="$pref "
      list2="${list2/$pref }"
  fi
done
array3=($preflist2$list2)
echo array1:
echo "${array1[*]}"
echo array2:
echo "${array2[*]}"
echo array3:
echo "${array3[*]}"

}
fn2 "${array1[@]}""${array2[@]}"
Having a problem passing in the arrays above in to the fn2 anyone spot why ?
 
Old 11-18-2011, 03:25 PM   #18
Juako
Member
 
Registered: Mar 2010
Posts: 202

Rep: Reputation: 84
I think the arrays aren't defined outside the function, at least in the code you post. When you try to pass them to the function you are passing "nothing" actually. Define the arrays in global scope if you want to have them available.

It would be really nice if you can format your code better, just indenting every block by 4 spaces would make marvels to allow anyone to read your code and understand it with less effort. Some comments or explanation of your approach won't hurt, either.

If your arrays will contain only elements that don't have spaces in them, I've posted two versions that work without associative arrays, and one without eval. Have you tried them?
 
Old 11-18-2011, 03:57 PM   #19
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
I'll second Juako's advice. Better formatting means easier to understand code and fewer errors.


And I think you're missing the point with functions. A function acts sort of like a script within a script. The arguments you pass to it are contained in function-local parameters, separate from the arguments you pass to the main script.

So, for example:

Code:
printit(){
	echo "$1"	#This is the function's $1
}

printit "$1"		#This is the script's $1
printit			#This prints nothing
I have to pass the $1 of the script into the $1 of the function for it to be available to the echo command.

Variables are a little more complex. If a variable is declared local to a function, then it's treated separately from the same name in the main script, if it exists. Otherwise it's treated as a global value for the whole script.

Code:
foo=bar
baz=bum

printit(){
	local foo	#foo is declared local, but baz is still global
	echo "$foo"
	echo "$baz"
	foo=baz
	baz=foo
}

printit		#will only print "bum", because the function's $foo is undefined.

echo "$foo"	#will still print "bar", since the function change was only local.
echo "$baz"	#will now print "foo, because the function affected the global variable.

So when you do this:
Code:
fn2 ()
{
array1=( $@ )
array2=( $@ )
}

fn2 "${array1[@]}" "${array2[@]}"
All you're passing all the values of both the main script's array1 and array2 into the function's $@.

Then, since you didn't set the function to use local arrays, it resets the main script's arrays, which means all you're doing is re-setting both arrays to the entire contents of both.

But since you didn't define the arrays in the outside script in the first place, they both end up containing nothing!


I'll have to look closer at the rest later tonight or tomorrow. I don't have any more time right now.
 
Old 11-18-2011, 04:26 PM   #20
Juako
Member
 
Registered: Mar 2010
Posts: 202

Rep: Reputation: 84
Delving a bit more in the code there are some more things:

Code:
fn2 () {

array1=( $@ )
array2=( $@ )
cnt=${#arr[@]}
echo "the count is " $cnt
for (( idx = 0 ; idx < cnt ; idx++ ))
do
  echo ${array2[idx]}
  echo ${array1[idx]}
  echo "the count ifor array2 "${#arr[@]} 
done
In the "cnt=${#arr[@]}" line, you are referring to $arr which isn't defined, so $cnt will be empty. The two lines that follow (echo and for) will then fail when trying to reference $cnt. You want probably ${#arr1[@]} or ${#arr2[@]}.

In the last line of the for loop (echo "the count ifor array2 ....) you again refer to $arr. Probably you meant ${#array2[@]}. And in case in the "cnt=" line mentioned above you wanted to refer to the same array as in this line, it would be redundant to assign ${#array2[@]} to $cnt in the first place if you later will use ${#array2[@]}.

Note that variables, substitutions, basically anything starting with $ can be included inside double quotes without problems:
Code:
echo "the count is " $cnt
echo "the count for array2 is "${#arr2[@]}
works the same as
Code:
echo "the count is $cnt"
echo "the count for array2 is ${#arr2[@]}"
 
Old 11-20-2011, 02:31 AM   #21
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
For purely educational purposes, let's break down nano2's previous script and see where the problems lie.

First, as requested, let's format it better. Without changing any of the actual commands, here's how I would format it. All operating blocks are indented with tabs to the same level, and related code blocks are separated by blank lines.

I also moved the do and then keywords on the same line as the for and if keywords. It cuts down on clutter and helps line up the block better, IMO.

Code:
#!/bin/bash

fn2 (){

     array1=( $@ )
     array2=( $@ )

     cnt=${#arr[@]}
     echo "the count is " $cnt

     for (( idx = 0 ; idx < cnt ; idx++ )); do

          echo ${array2[idx]}
          echo ${array1[idx]}
          echo "the count ifor array2 "${#arr[@]}

     done

     list2="${array2[*]}"
     cnt=${#array1[@]}

     for ((i=0; i <cnt ; i++ )); do
          pref=${array1[i]}
          if grep -wq $pref <<< "$list2"; then
               preflist2+="$pref "
               list2="${list2/$pref }"
          fi
     done

     array3=($preflist2$list2)

     echo array1:
     echo "${array1[*]}"
     echo array2:
     echo "${array2[*]}"
     echo array3:
     echo "${array3[*]}"

}

fn2 "${array1[@]}""${array2[@]}"
Now starting at the top:

Code:
fn2 (){
     array1=( $@ )
     array2=( $@ )
This sets both arrays, the global arrays, mind you, to the same values -- the entire "$@" function input. We don't really want them to be the same, do we? I think you want array1 to have the first input and array2 to have the second input. A simple technique is to use $1 and $2 as Juako did, but that relies on word-splitting the input, which means it would break if any of the values contained spaces.

Code:
     cnt=${#arr[@]}
     echo "the count is " $cnt
Juako has already discussed the problems here. The array name is wrong, and the quotes can, and indeed should, surround the variable as well (remember, word-splitting occurs after variable expansion, unless quoted).


Code:
     for (( idx = 0 ; idx < cnt ; idx++ )); do

          echo ${array2[idx]}
          echo ${array1[idx]}
          echo "the count ifor array2 "${#arr[@]}

     done
First of all, this whole section is really superfluous except as a way to check your inputs, since you print out the raw arrays at the end of the function anyway.

All this loop does is print out the entire contents of each array, and the count, one entry per line, interlaced. See the output lines at the end for how to print everything out more cleanly.

Also, the final echo will simply print the same count number over and over. You don't want that, do you? Especially since you've already echoed it once outside the loop.

Code:
list2="${array2[*]}"
I'll comment later about using a scalar variable as a way to process a list. Otherwise no real problems here.

Code:
cnt=${#array1[@]}
Be aware that this overwrites the cnt that was set before. It might be better to save the two array lengths to separate variables, so that you don't get them confused (although recycling a variable has its benefits too).

Code:
     for ((i=0; i <cnt ; i++ )); do
          pref=${array1[i]}
A superfluous use of a variable (pref), but no problems per-se.

Code:
          if grep -wq $pref <<< "$list2"; then
This works too. But calling on the external grep is completely unnecessary when the shell has everything you need to compare the strings built-in. Also, remember to quote the pref variable to avoid word-splitting.

Code:
               preflist2+="$pref "
               list2="${list2/$pref }"
It's at this point where I believe you really should be saving the output to another array. Scalar variables are not really suited for saving lists of things. But I'll save my recommended solution for a follow-up post.

Also, this technique removes duplicated entries if they match the default list, but keeps duplicates of the non-default entries. How the script should handle duplicates is something you still need to define for us.

Code:
     array3=($preflist2$list2)
Again, no problems here, but only because you ensured that the entries were separated by spaces in the lines above it. If there were no spaces at the end of preflist2, then the final word would be combined with the first word of list2, and you'd get a false entry like "cowrat". So put a space between the variables.

Also, since the array setting relies on word-splitting, it would again fail if individual entries contained spaces. This is exactly why I recommend always using arrays for all lists.

The line would also read more cleanly if you spaced it out better:
array3=( $preflist2 $list2 ).

Code:
fn2 "${array1[@]}""${array2[@]}"
Again, the only real problem with this line is that you never defined array1 and array2 before executing the function, so you're passing it nothing. But also, you have to be clear about how the function will use the input, which brings us back to the first two lines of fn2. How can the function know where to separate the input it receives into its array1 and array2?

And that goes back to my previous post where we must be clear about the difference between a function-local array and a global array of the same name.

Now if you a) define the global arrays first, b) declare the function's arrays to be local, and c) set it so that local array1 is set from local $1 and local array2 is set from local $2...then it would be possible to use the ${array[*]} form to send the global arrays into the local arrays reasonably safely.

But this, again, relies on word-splitting the input, and so is not the strongest method to use.

Anyway, to end this lesson, Your function will work as expected if you make the following changes:

Code:
fn2 (){
	local array1 array2
	array1=( $1 )
	array2=( $2 )
	...
}

globalarray1=( dog cat lion cow )
globalarray2=( lion rat mouse dog zebra pet cat bird cow calf sheep )

fn2 "${globalarray1[*]}" "${globalarray2[*]}"
...well mostly as expected. The part where it echos the arrays is still broken, due to the bad cnt variable. I suggest you simply remove that section entirely. But you should get the desired output otherwise.

Still, it's far from ideal due to the other weaknesses I've pointed out. Give me some time and I'll see about posting a version that takes all of this into account.
 
Old 11-20-2011, 02:53 AM   #22
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
I'd now like to ask nano2 to clarify the script requirements before we go on.
  1. What should happen if there are duplicates in the input list? For example, what should the output for this line be?
    Code:
    dog lion dog lion sheep cat sheep cat
  2. Do we need to worry about any of the input entries containing spaces? Could there be entries like mountain lion or water buffalo or such? What about capitalization? Should cat, Cat, and CAT be considered the same, or different?

  3. Do we need to worry about keeping the (global) input arrays intact? In other words, is this a stand-alone script, or do you need it to sit inside a larger script and process its input, and the originals need to remain untouched?

  4. Can/should the default entries be hard-coded in the script itself, or do you need them to be fed into the function from outside? And if from the outside, do they have to be passed directly as arguments to the script or function, or can they be taken from another source such as a text file?

The answers to all of these affect the final form of the script. Please take some time to answer them, so we understand your needs. Thanks.
 
1 members found this post helpful.
  


Reply

Tags
bash scripting



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 On
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
[SOLVED] Bash: Calculating in array and storing result in array ghantauke Linux - Newbie 6 12-02-2010 12:28 PM
Server array (mysql, php) -> gboolean array in c client kalleanka Programming 1 07-27-2010 06:50 AM
[SOLVED] shell script help: copying directory list into an array and then accessing the array richman1234 Linux - Newbie 6 07-25-2010 11:19 PM
[bash] indirect array reference to array with values containing spaces Meson Linux - Software 9 06-04-2010 09:38 PM
bash: use file as input into array, parse out other variables from array using awk beeblequix Linux - General 2 11-20-2009 10:07 AM

LinuxQuestions.org > Forums > Linux Forums > Linux - General

All times are GMT -5. The time now is 03:50 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