46

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?

Thor
  • 45,082
  • 11
  • 119
  • 130
yazz.com
  • 57,320
  • 66
  • 234
  • 385

7 Answers7

82

With bash

a="The cat sat on the mat"
b=cat
strindex() { 
  x="${1%%"$2"*}"
  [[ "$x" = "$1" ]] && echo -1 || echo "${#x}"
}
strindex "$a" "$b"   # prints 4
strindex "$a" foo    # prints -1
strindex "$a" "ca*"  # prints -1
Bruno
  • 580
  • 5
  • 14
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
  • 7
    +1 It also works in Dash, ash, ksh, pdksh, zsh. Dash and ash want `[ "$x" = "$1" ]` and pdksh wants `x=$2; x="${1%%$x*}"`, however. – Dennis Williamson Feb 17 '11 at 20:22
  • 8
    @Zubair, bash 2.0 is 10 years old and 2 major releases behind (http://ftp.gnu.org/gnu/bash/). Can you update it? – glenn jackman Mar 17 '11 at 13:47
  • 3
    This is just brilliant. The first parameter substitution expression says "delete from the search expression on to the end", and the ${#x} is the length of what remains - which is the position of the search expression! – Greg Bell Feb 27 '17 at 22:37
  • great answer, been using it for a while. just found out that a `*` in your search string will be interpreted as a wild card unless it is manually escape. i added a copy of your answer with automatic escaping, but all credit to you. https://stackoverflow.com/a/69960043/912236 – Orwellophile Nov 14 '21 at 03:29
  • 1
    @Orwellophile, to avoid pathname expansion of `$var` in `"${param%$var}"`, the easiest is to quote it: `"${param%"$var"}"`. See [here](https://www.shellcheck.net/wiki/SC2295). – Bruno Oct 06 '22 at 13:21
36

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 cat
4: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}'
Cercerilla
  • 1,353
  • 12
  • 13
10

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)}'
Nikita Rybak
  • 67,365
  • 22
  • 157
  • 181
  • 1
    awk gives +1 too great answer considering what the original poster requested. Howerer, there is a way to correct it: `awk -v a="$a" -v b="$test" 'BEGIN{print index(a,b)}' | xargs expr -1 +` – jarno Apr 01 '15 at 14:50
5
echo $a | grep -bo cat | sed 's/:.*$//'
Thor
  • 45,082
  • 11
  • 119
  • 130
qbert220
  • 11,220
  • 4
  • 31
  • 31
1

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: -f2
5

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

function strindex() {
    local str=$1
    local substr=$2
    echo -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.

8c6b5df0d16ade6c
  • 1,910
  • 15
  • 15
1

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=$1
  needle=$2
  x="${haystack%%"$needle"*}"
  [[ "$x" = "$haystack" ]] && { echo -1; return 1; } || echo "${#x}"
}

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

startswith() { 
  haystack=$1
  needle=$2
  x="${haystack#"$needle"}"
  [[ "$x" = "$haystack" ]] && return 1 || return 0
}

endswith() { 
  haystack=$1
  needle=$2
  x="${haystack%"$needle"}"
  [[ "$x" = "$haystack" ]] && return 1 || return 0
}

Orwellophile
  • 13,235
  • 3
  • 69
  • 45
0

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

it will return 5

Deepayan
  • 1
  • 1