1

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?

3
  • 2
    Debatable if it's idiomatic, but case $haystack in (*$needle*) echo yes;; (*) echo no;; esac is POSIX. The ( are optional, but I like them. Note grep treats 'needle' as a regexp (BRE) with . special but ? literal not special as in shell pattern including bash [[, also leading ^ and trailing $ special, and [charlist] similar but not identical. Commented Aug 19, 2022 at 7:22
  • Are you asking for a way without piping or for the most idiomatic way? My go-to for POSIX is with grep, but see this thread. Commented Aug 22, 2022 at 0:16
  • How did you get the nab @root as a username? That's awesome. I'm root on npm and I tried to get it on github. I envy you. :) Yes, I'm asking for both. Commented Aug 22, 2022 at 15:40

1 Answer 1

2

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

  • ✅ no subshell
  • ✅ no pipe
  • 🤷‍♂️ simplest, most idiomatic? maybe
#!/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'
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.