LinuxQuestions.org
Visit Jeremy's Blog.
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 08-25-2013, 12:35 PM   #1
Lucien Lachance
Member
 
Registered: May 2013
Posts: 82

Rep: Reputation: Disabled
Bash - passing associative arrays as arguments


I'm trying to replicate this function I've written in Python that prints a message based on the player and opponents move and compares those moves with an associative array called match. How can I pass a key array to a function in bash? I've declared match in my main function and I need to use this in another function which looks like this:
Code:
#!/bin/bash
declare -A match=(['r']='s' ['p']='r' ['s']='p')
Code:
def compare_moves(player, opponent, match):
    """Compares the moves of player and opponent.

    To determine a winner, we need to be aware of
    the move of player, as well as the opponent's
    move. We can then cross check the results of
    both moves against match and determine the
    winner of the game with the following: "rock"
    beats scissors, "paper" beats rock, "scissors"
    beats paper.

    Args:
        player: The decision and/or move of player
            determines the outcome of the game.
        opponent: A decision at random of opponent
            which is played against the player.
        match: The winning outcomes of the game
            that gets compared to both moves made.
    """
    if player == opponent:
        print "It appears we have a tie!"
    elif match[player] == opponent:
        print "Congratulations, you win!"
    else:
        print "You walk away with shame."
 
Old 08-25-2013, 03:31 PM   #2
konsolebox
Senior Member
 
Registered: Oct 2005
Distribution: Gentoo, Slackware, LFS
Posts: 2,248
Blog Entries: 8

Rep: Reputation: 235Reputation: 235Reputation: 235
What do you mean about key array?
 
Old 08-25-2013, 03:32 PM   #3
dwhitney67
Senior Member
 
Registered: Jun 2006
Location: Maryland
Distribution: Kubuntu, Fedora, RHEL
Posts: 1,541

Rep: Reputation: 335Reputation: 335Reputation: 335Reputation: 335
Quote:
Originally Posted by Lucien Lachance View Post
How can I pass a key array to a function in bash?
Perhaps something like this:
Code:
#!/bin/bash

function myFunction
{
    eval "declare -A ref"=${1#*=}

    for key in ${!ref[@]}; do
        echo -n "$key "
    done
    echo

    for value in ${ref[@]}; do
        echo -n "$value "
    done
    echo
}

declare -A match=(['r']='s' ['p']='r' ['s']='p')

myFunction "$(declare -p match)"
 
Old 08-25-2013, 03:49 PM   #4
Lucien Lachance
Member
 
Registered: May 2013
Posts: 82

Original Poster
Rep: Reputation: Disabled
Yeah, that's the only thing I've been able to find. I have three arguments in my case. Off the top of my head, this is the only way I could think of how it could be done. I just would like to know how can I pass in this associative array with my function compare_moves. Is there a better way of handling this?

Code:
compare_moves() {
  local player_move=$1
  local enemy_move=$2
  eval "declare -A compare="${3#*=}

  if [[ $player == $opponent ]]; then
    echo 'It appears we have a tie'
  elif [[ ${compare[$player]} == $opponent ]]; then
    echo 'Congratulations, you win!'
  else
    echo 'You walk away with shame.'
  fi
}

Last edited by Lucien Lachance; 08-25-2013 at 03:53 PM.
 
Old 08-25-2013, 06:53 PM   #5
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,005

Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
I am a little lost?? Why would you need to pass the array? Are you saying you have multiple functions that will all need access to this 'match' array? (seems unlikely, but I could be wrong)

I would have said that this would be the only function needing access to this array, hence simply declare it in this function.
You could also make it a global array and then you can access it from any function or part of your code (personally, not my first choice)
 
Old 08-25-2013, 06:55 PM   #6
Lucien Lachance
Member
 
Registered: May 2013
Posts: 82

Original Poster
Rep: Reputation: Disabled
I need this function to access this array. Sorry for the confusion. I thought about declaring it inside of the function, but I would prefer to keep it the same way as I had wrote it in Python. @grail
 
Old 08-26-2013, 01:27 AM   #7
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,005

Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
As Python is a higher level language it would be obvious not all things will be directly transferable. I would say that even in the Python example, whilst it has worked, I fail to see the need to pass the array if this is to be the only place a comparison is to be done, ie if the calling entry simply calls the command and passes the array which is not used anywhere but for this check, what advantage
do you gain except to use a feature?

May I suggest an alternative, if the array is indeed used elsewhere, why not simply pass the result from the array to the function that performs the test?
Code:
compare_moves() {
  local player_move=$1
  local enemy_move=$2
  local compare=$3     # third will be value of ${match[$player]}

  if [[ $player == $opponent ]]; then
    echo 'It appears we have a tie'
  elif [[ $compare == $opponent ]]; then
    echo 'Congratulations, you win!'
  else
    echo 'You walk away with shame.'
  fi
}
 
Old 08-26-2013, 06:08 AM   #8
konsolebox
Senior Member
 
Registered: Oct 2005
Distribution: Gentoo, Slackware, LFS
Posts: 2,248
Blog Entries: 8

Rep: Reputation: 235Reputation: 235Reputation: 235
@Lucien Lachance Why not just access your array globally from the function?

Code:
declare -A match=(['r']='s' ['p']='r' ['s']='p')

compare_moves() {
  local player_move=$1
  local opponent_move=$2
  ...
  elif [[ ${match[$player_move]} == "$opponent_move" ]]; then
    echo 'Congratulations, you win!'
  ...
}

compare_moves "$player_move" "$opponent_move"
And it's recommendable to quote your second variable in conditional expressions of [[ ]].
 
Old 08-26-2013, 08:21 AM   #9
Lucien Lachance
Member
 
Registered: May 2013
Posts: 82

Original Poster
Rep: Reputation: Disabled
@grail everything is declared in my main function like so. I just want to be able to directly access table because it's already declare in main. So the function you've written I can use by saying
Code:
 compare_moves "$player" "$enemy" "${table[$player]}"
correct? I'll also provide some more clarity so you can have an idea as to what I've set up:

Code:
print_all() {
  eval "declare -A move_set="${1#*=}
  for move in "${!move_set[@]}"; do
    printf "%s => %s\n" "$move" "${move_set[$move]}"
  done
}

main() {
  local reply='y'
  local player enemy
  declare -A plays=(['r']='rock' ['p']='paper' ['s']='scissors')
  declare -A table=(['r']='s' ['p']='r' ['s']='p')

  print_all "(declare -p plays)" # Prints all available moves
  # code continues...
}

Last edited by Lucien Lachance; 08-26-2013 at 08:22 AM.
 
Old 08-26-2013, 10:34 AM   #10
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,005

Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
Yes you have the correct idea for the function.

As I said before though, bash is not OO orientated like Python so not all constructs will convert directly.
May I ask, is your intention to learn how to translate from Python to bash or how to implement the same solution (ie the game) in bash?

If the latter, then suggestion from konsolebox and myself earlier would fit with a bash implementation. Also, most (not all but a large number I have seen and implemented) generally
have the list of functions and then 'main' is not a function but the end part of the script. This would then still fit your format but all those items created in the main, such as
plays array, would all be globally accessible within any function you require them.

As a side note (and I may get a little flamed for this), however, most scripters steer away from using eval. Like any command it can be used safely and for the likes of a game / script that
only you will play and hence no issue with malicious usage occurring it poses no real threat. That being said, I use it sparingly and with extreme caution as to become reliant on it,
and hence use as a standard feature of any script can be hazardous (from someone who had half a system wiped from such use).
 
Old 08-26-2013, 11:02 AM   #11
Lucien Lachance
Member
 
Registered: May 2013
Posts: 82

Original Poster
Rep: Reputation: Disabled
I see, what would you suggest I use to handle print_all? I have one idea. I could make a one-liner and use declare -p and pipe the output to awk to format the array output.
 
Old 08-26-2013, 12:19 PM   #12
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,005

Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
Well under the global landscape idea you would simply use the plays array in the print_all function:
Code:
print_all() {
  for move in "${!plays[@]}"; do
    printf "%s => %s\n" "$move" "${plays[$move]}"
  done
}
I guess then you have to look at coding style, generally I would create a function for a repetitive task that needs to be called several times throughout the code.
Looking at this snippet, I cannot see why you would need to call it again?

If the idea is to present all the options to the users at the start of each game then simply place the for loop inside a while loop that checks when the game is to end.
 
Old 08-26-2013, 12:43 PM   #13
Lucien Lachance
Member
 
Registered: May 2013
Posts: 82

Original Poster
Rep: Reputation: Disabled
This function is only called on each iteration of a while loop I've set up. It's more of a display function than anything. I'll go with your idea of just writing the loop without using a function. I'm so used to writing C and Python and having everything in small compartments and/or modules. I also wrote a check to make sure the user's response is within the key values of plays which represents: r, p, and s. @grail
 
  


Reply

Tags
bash, linux, shell scripting


Thread Tools Search this Thread
Search this Thread:

Advanced Search

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
[SOLVED] BASH - passing arguments Garrett85 Programming 4 01-09-2013 03:03 PM
LXer: Bash Associative Arrays LXer Syndicated Linux News 0 04-28-2010 10:20 PM
bash: Passing arrays with spaces jakeo25 Programming 8 06-05-2009 08:00 AM
LXer: How To Fake Associative Arrays In Bash LXer Syndicated Linux News 1 06-05-2008 11:01 AM
bash, passing arguments Four Linux - Newbie 3 02-06-2006 08:24 AM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

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