Search code examples
arraysbashbats-core

How to check order of two elements in bash array?


Context

After having written a method that checks whether element element_one occurs before element element_two in a comma separated list (in string format in Bash), I am experiencing some difficulties in converting the code and tests into a function that:

  1. Verifies element element_one is in the bash array.
  2. Verifies element element_two is in the bash array.
  3. Verifies element element_one occurs before element element_two in the array.

Code

element_one_before_two_in_csv() {
  local elem_one
  elem_one="$1"
  local elem_two
  elem_two="$2"
  local csv_array
  csv_array="$3"

  IFS=, read -r -a arr <<<"${csv_array}"
  local found_one
  local found_two
  
  assert_csv_array_contains_element "$elem_one" "$csv_array"
  assert_csv_array_contains_element "$elem_two" "$csv_array"

  for item in "${arr[@]}"; do
    if [[ $elem_one == "$item" ]]; then
      found_one="FOUND"
    fi
    if [[ $elem_two == "$item" ]]; then
      found_two="FOUND"
    fi

    if [[ "$found_two" == "FOUND" ]] && [[ "$found_one" != "FOUND" ]]; then
      echo "AFTER"
      break
    elif [[ "$found_one" == "FOUND" ]] && [[ "$found_two" != "FOUND" ]]; then
      echo "BEFORE"
      break
    fi
  done
  
}

assert_csv_array_contains_element() {
  local elem_one
  elem_one="$1"
  local csv_array
  csv_array="$2"

  if [[ "$(csv_array_contains_element $elem_one $csv_array)" != "FOUND" ]]; then
      echo "Error, did not find element:$elem_one in: $csv_array"
      exit 6
  fi
}

csv_array_contains_element() {
  local elem_one
  elem_one="$1"
  local csv_array
  csv_array="$2"

  IFS=, read -r -a containing_arr <<<"${csv_array}"
  local found_one
  for item in "${containing_arr[@]}"; do
    if [[ "$elem_one" == "$item" ]]; then
      found_one="FOUND"
      echo "FOUND"
    fi
  done
  if [[ $found_one != "FOUND" ]]; then
      echo "NOTFOUND"
  fi
}

Tests

#!./test/libs/bats/bin/bats

load 'libs/bats-support/load'
load 'libs/bats-assert/load'

@test "element_one_before_two_in_csv returns BEFORE for correct found order." {
  source ./src/helper.sh
  
  # Run function that is tested.
  run element_one_before_two_in_csv "two" "three" "one,two,three,four"
  assert_output "BEFORE"
  
}

@test "element_one_before_two_in_csv returns AFTER for swithced order." {
  source ./src/helper.sh

  run element_one_before_two_in_csv "three" "two" "one,two,three,four"
  assert_output "AFTER"
}


@test "element_one_before_two_in_csv raises error if first element is missing." {
  source ./src/helper.sh

  run element_one_before_two_in_csv "banana" "two" "one,two,three,four"
  assert_failure
    assert_output -p "Error, did not find element:banana in: one,two,three,four"
}

@test "element_one_before_two_in_csv raises error if second element is missing." {
  source ./src/helper.sh

  run element_one_before_two_in_csv "four" "banana" "one,two,three,four"
  assert_failure
    assert_output -p "Error, did not find element:banana in: one,two,three,four"
}

Output

bats test
test_array_order.bats
 ✓ element_one_before_two_in_csv returns BEFORE for correct found order.
 ✓ element_one_before_two_in_csv returns AFTER for switched order.
 ✓ element_one_before_two_in_csv raises error if first element is missing.
 ✓ element_one_before_two_in_csv raises error if second element is missing.

4 tests, 0 failures

Question

Hence, I would like to ask: *How can a function in bash check whether some element one occurs before some element two in a bash array (whilst verifying both elements one and two are in the array)?


Solution

  • Try bash-regexp:

    $ arr=(one two)
    $ [[ ${arr[@]} =~ one.*two ]] && echo ${BASH_REMATCH[@]}
    one two
    
    $ arr=(two one)
    $ [[ ${arr[@]} =~ one.*two ]] && echo ${BASH_REMATCH[@]}