-1

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

a.t.
  • 2,002
  • 3
  • 26
  • 66
  • 1
    Checking if element in array: https://stackoverflow.com/questions/3685970/check-if-a-bash-array-contains-a-value, for checking order, you have to loop. – Nic3500 Feb 13 '23 at 16:00
  • 1
    If this is something you'll have to do multiple times while your code is running, consider populating an associative array when you save the existing array elements, e.g. you already have `idx2val["$idx"]="$val"` somewhere so also add `val2idx["$val"]="$idx"` so you can look up the index for each value in the second array and compare the results without looping. You could also populate that 2nd array once in a loop after populating the first one if that's a better approach. – Ed Morton Feb 13 '23 at 16:16
  • @M.NejatAydin doesn't that assume that they are unique and no common suffix? `a b b,a,b`, `c d ac,d,c` – jhnc Feb 13 '23 at 17:03
  • 1
    What difficulties are you having? I see some code, and an apparently passing test suite. Is one of the tests wrong? – chepner Feb 13 '23 at 17:04

1 Answers1

1

Try bash-regexp:

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

$ arr=(two one)
$ [[ ${arr[@]} =~ one.*two ]] && echo ${BASH_REMATCH[@]}
Ivan
  • 6,188
  • 1
  • 16
  • 23
  • 1
    `arr=( "one mississippi" "two mississippi" ) ; [[ ${arr[@]} =~ one.*two ]] && echo ${BASH_REMATCH[@]}` prints `one mississippi two`. May not be exactly what OP wants. – tjm3772 Feb 13 '23 at 17:58
  • 1
    The specific problem being, `one mississippi` isn't an exact match to `one` but the regex allows for substring matches within elements as long as an element with `one` in it somewhere occurs before an element with `two` in it somewhere. – tjm3772 Feb 13 '23 at 18:06
  • AFAIU OP wants to just check, that elements present in array and are in certain order. The output I've provided with `echo ${BASH_REMATCH[@]}` is needed just for illustration of the process. And regex could be ajusted too. – Ivan Feb 13 '23 at 19:17