1

What would be Zsh equivalent of bash's

expr index "abcdefghijklmnopqrstuvwxyz" xyzd

I am trying to find index of substring.

  • 2
    expr isn't a builtin. Why not use the exact same syntax? – Nick ODell Jul 31 '18 at 4:27
  • The expr utility doesn't have an index operation. Or are you using GNU expr maybe? – Kusalananda Jul 31 '18 at 5:39
3

If you're looking for a zsh builtin alternative to that non-standard expr usage (nothing to do with bash btw), you could do:

$ a=abcdefghijklmnopqrstuvwxyz b=xyzd
$ echo ${a[(i)[$b]]}
4

With i, you get the index of the first match (or one plus the length of the haystack if there's no match). With I instead, you get the last match (or 0 if there's no match).

With any POSIX-like shell (including bash and zsh)

$ s=${a%%[$b]*}
$ echo "$(($#s + 1))"
4
3

The implementation of expr found in the GNU coreutils package is an external utility and can be used in the same way regardless of what shell you are using.

In zsh,

expr index "abcdefghijklmnopqrstuvwxyz" xyzd

would return 4, just as in bash or any other shell.

The index operation in GNU's expr is an extension to the standard expr, which means you'd likely to get an expr: syntax error error on systems without that particular implementation of expr. This is regardless of what shell you use.

  • index is not a GNU extension. SysIII expr in 1980 had it. It's true it's not standard and not supported everywhere. – Stéphane Chazelas Jul 31 '18 at 8:06
  • @StéphaneChazelas Thanks. You're better at the history than I am. Slight modification to the answer... – Kusalananda Jul 31 '18 at 8:08
  • Technically, expr index "abcdefghijklmnopqrstuvwxyz" xyzd would return a different result in the rc shell or derivatives as " is not a special quoting character in the syntax of those shells. – Stéphane Chazelas Jul 31 '18 at 8:13
  • @Kusalananda expr: syntax error is exactly what I am getting. – fg78nc Jul 31 '18 at 12:20
  • 1
    @fg78nc That means that your expr is not GNU coreutils' expr. Either install GNU coreutils on your Unix (the command might then be called gexpr rather than expr) or find an alternative way of performing the operation. – Kusalananda Jul 31 '18 at 14:49
2

expr index "abcdefghijklmnopqrstuvwxyz" xyzd

I am trying to find index of substring.

Index of a substring, or of a character? expr index looks for any of the characters given in the second set, i.e. the above finds the position of the first d, not the substring xyzd (which doesn't exist in your string).

To look for the position of a substring, you could do, in standard shell:

haystack=abcdefghijklmnopqrstuvwxyz
needle=jkl
x=${haystack%%$needle*};

Now x contains the part of string before that substring, so "${#x}" is the zero-based position of jkl (9, in this case).

To look for the position of a character (byte) from a set of characters, you could similarly use:

haystack=abcdefghijklmnopqrstuvwxyz
needle=xqe;
x=${haystack%%[$needle]*}; 

Here, "${#x}" is 4. The downside of both of these is that if the string/character being searched for isn't found, x contains the whole of haystack, so you need to compare "${#x}" against the full length of haystack (not zero) to see if a match was found:

haystack=abcdefghijklmnopqrstuvwxyz
needle=qqq
x=${haystack%%$needle*}
if [ "${#x}" = "${#haystack}" ]; then echo "$needle not found"; fi
0

As Kusalananda already pointed out in his comment, you can of course use expr in zsh too, but I guess you meant whether Zsh has a builtin way to do it.

The answer is "no", but it contains something similar:

v="abcdefghijklmnopqrstuvwxyz"
i=${(SB)v#cdef}

This would set i to 3. However, there are two reasons why this doesn't work exactly the same as expr index:

In the example you gave, you are searching for xyzd, which is not present in your string, but since your string ends in xyz, expr gracefully drops the 'd' and returns a match. One might argue that the behaviour of Zsh is more reasonable in this respect, but of course if this an important issue for you, I would stick with Zsh.

The other reason why Zsh is different, is that ${(SB)v#...} returns 1 if the string does not match. For example,

echo ${(SB)v#a}
echo ${(SB)v#7}

would both return 1. With this alone, you can't distinguish between a non-match and a match at the first character. One possible solution is to have it calculate the position and the length of the match:

# N returns the length of the match
index_and_length=${(SBN)v#...}
# index_and_length contains the index, a space, and the length
if [[ ${index_and_length#* } == 0 ]]
then
  echo no match
else
  index=${index_and_length% *}
  echo match at index $index
fi

UDPATE: As the comments to this post reveal, I misunderstood the purpose of expr index; hence, my answer, as given, is inadequate. However we can adapt it, because the "string" to be searched for, is a glob-pattern. Hence, square brackets will do the job:

# Search for the first occurance of one of the letters x y z d
index_and_length=${(SBN)v#[xyzd]}
  • 1
    The user's command would return the index of the first character found from the second string in the first. The d is the first one found, and it's at position 4 in the first string. That's how I think it works anyway. Or it's the index of the d in the second string... I don't have the manual here atm. – Kusalananda Jul 31 '18 at 7:44
  • 1
    It looks for single characters: "index STRING CHARS: index in STRING where any CHARS is found, or 0" -- man7.org/linux/man-pages/man1/expr.1.html – ilkkachu Jul 31 '18 at 7:59
  • @Kusalananda : Thank you for pointing this out. I amended my answer to fit this interpretation. – user1934428 Jul 31 '18 at 8:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.