LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   Bash - passing associative arrays as arguments (https://www.linuxquestions.org/questions/programming-9/bash-passing-associative-arrays-as-arguments-4175474655/)

Lucien Lachance 08-25-2013 12:35 PM

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."


konsolebox 08-25-2013 03:31 PM

What do you mean about key array?

dwhitney67 08-25-2013 03:32 PM

Quote:

Originally Posted by Lucien Lachance (Post 5015666)
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)"


Lucien Lachance 08-25-2013 03:49 PM

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
}


grail 08-25-2013 06:53 PM

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)

Lucien Lachance 08-25-2013 06:55 PM

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

grail 08-26-2013 01:27 AM

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
}


konsolebox 08-26-2013 06:08 AM

@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 [[ ]].

Lucien Lachance 08-26-2013 08:21 AM

@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...
}


grail 08-26-2013 10:34 AM

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).

Lucien Lachance 08-26-2013 11:02 AM

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.

grail 08-26-2013 12:19 PM

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.

Lucien Lachance 08-26-2013 12:43 PM

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


All times are GMT -5. The time now is 08:53 AM.