If I have the text in a shell variable, say $a:

a="The cat sat on the mat"

How can I search for "cat" and return 4 using a Linux shell script, or -1 if not found?

8

Best Answer


With bash

a="The cat sat on the mat"b=catstrindex() { x="${1%%"$2"*}"[[ "$x" = "$1" ]] && echo -1 || echo "${#x}"}strindex "$a" "$b" # prints 4strindex "$a" foo # prints -1strindex "$a" "ca*" # prints -1

You can use grep to get the byte-offset of the matching part of a string:

echo $str | grep -b -o str

As per your example:

[user@host ~]$ echo "The cat sat on the mat" | grep -b -o cat4:cat

you can pipe that to awk if you just want the first part

echo $str | grep -b -o str | awk 'BEGIN {FS=":"}{print $1}'

I used awk for this

a="The cat sat on the mat"test="cat"awk -v a="$a" -v b="$test" 'BEGIN{print index(a,b)}'
echo $a | grep -bo cat | sed 's/:.*$//'

This is just a version of the glenn jackman's answer with escaping, the complimentary reverse function strrpos and python-style startswith and endswith function based on the same principle.

Edit: updating escaping per @bruno's excellent suggestion.

strpos() { haystack=$1needle=$2x="${haystack%%"$needle"*}"[[ "$x" = "$haystack" ]] && { echo -1; return 1; } || echo "${#x}"}strrpos() { haystack=$1needle=$2x="${haystack%"$needle"*}"[[ "$x" = "$haystack" ]] && { echo -1; return 1 ;} || echo "${#x}"}startswith() { haystack=$1needle=$2x="${haystack#"$needle"}"[[ "$x" = "$haystack" ]] && return 1 || return 0}endswith() { haystack=$1needle=$2x="${haystack%"$needle"}"[[ "$x" = "$haystack" ]] && return 1 || return 0}

This can be accomplished using ripgrep (aka rg).

❯ a="The cat sat on the mat"❯ echo $a | rg --no-config --column 'cat'1:5:The cat sat on the mat❯ echo $a | rg --no-config --column 'cat' | cut -d: -f25

If you wanted to make it a function you can do:

function strindex() {local str=$1local substr=$2echo -n $str | rg --no-config --column $substr | cut -d: -f2}

...and use it as such: strindex <STRING> <SUBSTRING>

strindex "The cat sat on the mat" "cat"5

You can install ripgrep on MacOS with: brew install --formula ripgrep.

A variation (bash) on @Orwellophile 's answer, done out the long way. However, it also does the comparison with string lengths instead of comparing strings. You never know how long a string might be! :-) Hopefully, while clearly longer, this answer will be clearer.

function strpos (){local -r needle="${1:?}" ## Prevents empty stringslocal -r haystack="${2:?}" ## Prevents empty strings## From a copy, attempts to remove characters from the end of a string, greedily.local -r remainingHaystack="${haystack%%"$needle"*}"local -ir remainingHaystackLength="${#remainingHaystack}"## When the needle is not found in haystack, these values will be equal.if (( $remainingHaystackLength == ${#haystack} )); thenecho -n -1return 1fiecho -n $remainingHaystackLength}

If the pattern matches a trailing portion of the expanded value of parameter, thenthe result of the expansion is the value of parameter with theshortest matching pattern (the ‘%’ case) or the longest matchingpattern (the ‘%%’ case) deleted.

Example:

If parameter = "/usr/bin/foo/bin", and word = "/bin"

 ${parameter%word} ## /usr/bin/foo/bin --> /usr/bin/foo (non-greedy)${parameter%%word} ## /usr/bin/foo/bin --> /usr (greedy)

If parameter is ‘@’ or ‘*’, the pattern removal operation is applied to each positional parameter inturn, and the expansion is the resultant list. If parameter is anarray variable subscripted with ‘@’ or ‘*’, the pattern removaloperation is applied to each member of the array in turn, and theexpansion is the resultant list.

Bash Reference Manual: Shell Expansion

Most simple is -expr index "The cat sat on the mat" cat

it will return 5