Marry Jane
Marry Jane

Reputation: 364

Break bash array into pairs, and match on either element

I have a bash script which loops through a list of arrays by 2 at a time, like this;

#!/bin/bash

declare -a arr=(
    "apple" "fruit"
    "lettuce" "vegetables"
    "almonds" "nuts"
)

for ((i=0; i<${#arr[@]}; i+=2)); do
    echo "${arr[i]} ${arr[i+1]}"
done

and when you run this script, it prints out;

apple fruit
lettuce vegetables
almonds nuts

I'm looking for a way to select a specific item from the array and only print that one, something like this;

./script.sh apple

and it will print out only the apple and the 2nd item with the apple;

apple fruit

or it can be the other way, like; ./script.sh fruit and it will print out;

apple fruit

and if no arguments given, just print out everything.

So I'm basically looking for a way to choose items from the array, is this possible to do with bash arrays?

Upvotes: 0

Views: 440

Answers (2)

David C. Rankin
David C. Rankin

Reputation: 84607

With the addition of a simple test to ensure you are (1) not matching the last element and (2) the current element equals the input parameter, you can output the current and next elements in the array with:

updated to output all if no positional parameter provided

#!/bin/bash

declare -a arr=(
    "apple" "fruit"
    "lettuce" "vegetables"
    "almonds" "nuts"
)

declare -i nelem=${#arr[@]}

for ((i = 0; i< nelem; i++)); do
    if [ -z "$1" ]; then    ## no argument given - output all
        echo "${arr[i]}"
    else
        ## test not last element and current equals input
        [ "$i" -lt $((nelem - 1)) ] && [ "${arr[i]}" = "$1" ] &&
        echo "${arr[i]} ${arr[$((i+1))]}"
    fi
done

Example Use/Output

$ bash a.sh apple
apple fruit

$ bash a.sh lettuce
lettuce vegetables

$ bash a.sh almonds
almonds nuts

$ bash a.sh nuts
(no output)

No input case:

$ bash a.sh
apple
fruit
lettuce
vegetables
almonds
nuts

Look things over and let me know if you have further questions.

Upvotes: 1

Charles Duffy
Charles Duffy

Reputation: 295696

First, as the simplest possible thing, taking your exact existing logic and just adding a conditional around the print operation:

#!/bin/bash

declare -a arr=(
    "apple" "fruit"
    "lettuce" "vegetables"
    "almonds" "nuts"
)

for ((i=0; i<${#arr[@]}; i+=2)); do
    if [[ ${arr[i]} = "$1" ]] || [[ ${arr[i+1]} = "$2" ]]; then
        echo "${arr[i]} ${arr[i+1]}"
    fi
done

However, if in your real-world use case you're going to be doing several invocations within the same script, it'll be worth the overhead to build a lookup table:

# initial setup, only needs to happen once
declare -a arr=(
    "apple" "fruit"
    "lettuce" "vegetables"
    "almonds" "nuts"
)
declare -A arrayFwd arrayRev
for ((i=0; i<${#arr[@]}; i+=2)); do
    arrayFwd[${arr[i]}]=${arr[i+1]}
    arrayRev[${arr[i+1]}]=${arr[i]}
done

lookup() {
  if [[ ${arrayFwd[$1]} ]]; then
    echo "$1 ${arrayFwd[$1]}"
  elif [[ ${arrayRev[$1]} ]]; then
    echo "${arrayRev[$1]} $1"
  fi
}

...after which you can cheaply run:

lookup apple

or

lookup fruit

...and performance will be identical no matter how many items are in the lookup table.

Upvotes: 3

Related Questions