posixalpine-linuxash

How to match a substring using Posix Shell?


What's an idiomatic way to test if a string contains a substring in a Posix Shell?

Basically this, but Posix:

[[ ${my_haystack} == *${my_needle}* ]]

Non-Posix Example

I'm looking for the equivalent of this, but that works in a Posix / Almquist / dash / ash shell:

#!/bin/bash

set -e
set -u

find_needle() {
    my_haystack="${1}"
    my_needle="${2}"

    if [[ ${my_haystack} == *${my_needle}* ]]; then
        echo "'${my_haystack}' contains '${my_needle}'"
    else
        echo "'${my_haystack}' does NOT contain '${my_needle}'"
    fi
}

find_needle "${1:-"haystack"}" "${2:-"a"}"

(that doesn't work in sh)

My ideal solution would be one that doesn't require the use of a subshell or pipe, and that doesn't exit on failure in strict mode.

Workaround

This works, but I'm wondering if there's another way to test a substring without echoing and piping to grep.

#!/bin/sh

set -e
set -u

find_needle() {
    my_haystack="${1}"
    my_needle="${2}"
    if echo "${my_haystack}" | grep -q "${my_needle}"; then
        echo "'${my_haystack}' contains '${my_needle}'"
    else
        echo "'${my_haystack}' does NOT contain '${my_needle}'"
    fi
}

find_needle "${1:-"haystack"}" "${2:-"a"}"

Or maybe this is the most idiomatic way?


Solution

  • Substring match with case

    As @dave_thompson_085 points out [1], you can use case:

    case $haystack in
        *$needle*)
            return 0
            ;;
        *)
            return 1
            ;;
    esac
    

    See also:

    Example Script

    #!/bin/sh
    
    set -e
    set -u
    
    test_substring() {
        haystack="${1}"
        needle="${2}"
    
        case $haystack in
            *$needle*)
                return 0
                ;;
            *)
                return 1
                ;;
        esac
    }
    
    find_needle() {
        my_haystack="${1}"
        my_needle="${2}"
        if test_substring "${my_haystack}" "${my_needle}"; then
            echo "'${my_haystack}' contains '${my_needle}'"
        else
            echo "'${my_haystack}' does NOT contain '${my_needle}'"
        fi
    }
    
    find_needle haystack a
    find_needle haystack x
    

    Output:

    'haystack' contains 'a'
    'haystack' does NOT contain 'x'