Parameterized comparisons (Bash)

Uit De Vliegende Brigade
Naar navigatie springen Naar zoeken springen

How to make a comparison so flexible, that the arguments could be provided as an argument to a function?

Context

  • I frequently use code to loop through an associative array with 'rows' (associative arrays in Bash don't really have rows) representing websites on a webserver. In this loop, these rows are filtered and sites that pass, are processed in some way
  • This filter is hardcoded. That's messy. I would like to completely abstract away this loop.

Some new insights (autumn 2023):

  • It would be nice if the filtered list of sites, would actually be another file, stored on the computer: GNU Parallel cannot handle associative arrays. I have examples where the data concerning the filtered sites, is subsequently copied to a normal array (or even a variable), but I think that using a file is actually easier for debugging
  • I don't like it at all, that the main part of these scripts is a loop and that a function is called from within this loop. I can't put my finger on it, but I really don't like loops. Maybe this is after I saw a YouTube video about 'never nesters'. Using GNU Parallel solves two problems at once: (1) Get rid of loops and (2) speed up execution.

Example of the filter withing this 'site loop':

if \
   [[ ${site_array[$i,tag]} =~ "_dvb8v2_" ]] && \
   [[ ${site_array[$i,tag]} =~ "_wc_"     ]] && \
   [[ ${site_array[$i,tag]} =~ "_top14_"  ]] && \
   [[ ${site_array[$i,language]} ="en"
   #
then
   ...
fi

Solutions

Separate comparison function

It might help to think of using a separate function that only contains the comparison and that returns a Boolean Yes or No - That makes stuff less complex. E.g.:

function filter_site_array_row()
{
    local conditions="$1"
    local tag="$2"

    if [[ $tag =~ $conditions ]]; then
        return 0  # Condition matched
    else
        return 1  # Condition not matched
    fi
}

eval

Bash has something like eval, where a variable can be executed as code. Very flexible, but quite impossible to debug + prone to errors. Security isn't an issue here.

Pass conditions & arguments

Simple example:

#!/bin/bash

filter_conditions() {
    local conditions="$1"
    local tag="$2"

    if [[ $tag =~ $conditions ]]; then
        return 0  # Condition matched
    else
        return 1  # Condition not matched
    fi
}

# Example usage:
site_array=(
    [0,tag]="_dvb8v2_bal_wp_wc_cb_top14_"
    [1,tag]="_dvb8v2_wp_wc_cb_top14_"
    [2,tag]="_dvb8v2_bal_wc_cb_top14_"
)

# Define conditions based on arguments passed to the script
conditions="_dvb8v2_.*_wp_.*_wc_.*_cb_.*_top14_"

# Loop through site_array and apply filter_conditions
for ((i = 0; i < ${#site_array[@]}; i++)); do
    if filter_conditions "$conditions" "${site_array[$i,tag]}"; then
        echo "Match found for index $i: ${site_array[$i,tag]}"
        # Do something with the matched tag
    fi
done

However, this can only handle comparisons concerning the tag field of rows. A more flexible approach:

#!/bin/bash

filter_conditions() {
    local conditions=("$@")
    local tag="${site_array[$i,tag]}"
    local language="${site_array[$i,language]}"

    for condition in "${conditions[@]}"; do
        if [[ $condition == "tag_"* ]]; then
            local field="${condition:4}"  # Extract the field name from the condition
            if [[ ! $tag =~ "${!field}" ]]; then
                return 1  # Condition not matched for tag field
            fi
        elif [[ $condition == "language_"* ]]; then
            local field="${condition:9}"  # Extract the field name from the condition
            if [[ "${!field}" != "en" ]]; then
                return 1  # Condition not matched for language field
            fi
        else
            # Invalid condition format
            echo "Invalid condition: $condition"
            return 1
        fi
    done

    return 0  # All conditions matched
}

# Example usage:
site_array=(
    [0,tag]="_dvb8v2_wc_top14_"
    [0,language]="en"
    [1,tag]="_dvb8v2_wp_wc_top14_"
    [1,language]="fr"
    [2,tag]="_dvb8v2_wc_top14_"
    [2,language]="en"
)

# Define conditions based on arguments passed to the script
conditions=(
    "tag_.*_wc_.*_top14_"  # Condition for tag field
    "language_en"          # Condition for language field
)

# Loop through site_array and apply filter_conditions
for ((i = 0; i < ${#site_array[@]}; i += 2)); do
    if filter_conditions "${conditions[@]}"; then
        echo "Match found for index $i: Tag - ${site_array[$i,tag]}, Language - ${site_array[$i,language]}"
        # Do something with the matched fields
    fi
done