Skip to content

Instantly share code, notes, and snippets.

@mattmc3
Last active May 28, 2025 19:19
Show Gist options
  • Save mattmc3/110eca74a876154c842423471b8e5cbb to your computer and use it in GitHub Desktop.
Save mattmc3/110eca74a876154c842423471b8e5cbb to your computer and use it in GitHub Desktop.
Zsh - string utilities

zsh strings

Fish has a utility for string maniplulation.

This is how you can do the same things with Zsh builtins.

References:

Length

Get the length of a string with #. This is similar to string length in fish.

$ str="abcdefghijklmnopqrstuvwxyz"
$ echo ${#str}
26
$

Pad/Trim

Left pad a string with the l expansion flag. Right pad a string with the r expansion flag. This is similar to string pad in fish.

$ str="abc"
$ echo ${(l(10)(-))str}
-------abc
$ echo ${(r(10)(ABC))str}
abcABCABCA
$

The docs can be confusing. They show the syntax as l:expr::string1::string2:, which uses colons instead of the more readable parens. Don't be confused by the double colon, which is really just the closing/opening combo )(. If you choose to follow the docs, the syntax looks like this:

$ str="abc"
$ echo ${(r:10::-:)str}
abc-------
$

Trim requires the use of sed. This is similar to string trim in fish.

$ str="   \t\t\t   abc   \t\t\t   "
$ echo "$str" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
abc
$

Trimming strings other than whitespace can be accomplished with the '#' and '%' parameter expansions.

# removes at the start of the string. # is the shortest match, and ## is the longest.

$ str="a/b/c/d/e/f/g"
$ echo ${str#*/}
b/c/d/e/f/g
$ echo ${str##*/}
g
$

% removes at the end of the string. % is the shortest match, and %% is the longest.

$ str="a/b/c/d/e/f/g"
$ echo ${str%/*}
a/b/c/d/e/f
$ echo ${str%%/*}
a
$

Substring

Get a substing from string with comma indexing [start,end]. This is similar to string sub in fish.

$ str="abcdefghijklmnopqrstuvwxyz"
$ echo ${str[3,6]}
cdef
$

You can also use the ${var:offset:length} syntax:

$ str="abcdefghijklmnopqrstuvwxyz"
$ echo ${str:3:6}
defghi
$ echo ${str:(-4)}
wxyz
$

Repeat

Repeat a string by using printf. This is similar to string repeat in fish.

$ str="abc"
$ abc3=$(printf "$str%.0s" {1..3})
$ echo $abc3
abcabcabc
$

Escape/Unescape

Escape (quote) strings with the q modifier. This is similar to string escape in fish.

$ str="3 tabs \t\t\t."
$ echo "${str:q}"
3\ tabs\ \t\t\t.
$

Unescape (unquote) strings with the Q modifier. This is similar to string unescape in fish.

$ str="3 backticks \`\`\`."
$ esc="${str:q}"
$ echo $esc
3\ backticks\ \`\`\`.
$ echo "${esc:Q}"
3 backticks ```.
$

Join/Split

Join strings with the j expansion flag. This is similar to string join in fish.

$ words=(abc def ghi)
$ sep=:
$ echo ${(pj.$sep.)words}
abc:def:ghi
$

A common join seperator is the null character. This is similar to string join0 in fish.

$ words=(abc def ghi)
$ sep="\x00"
$ echo ${${(pj.$sep.)words}:q}
abc\x00def\x00ghi
$

Split strings with the s expansion flag. This is similar to string split in fish.

  • @: Preserves empty elements. "In double quotes, array elements are put into separate words".
  • p: Use print syntax. "Recognize the same escape sequences as the print."
  • s: Split. "Force field splitting at the separator string."
$ str="a:b::c"
$ sep=:
$ printf '%s\n' "${(@ps.$sep.)str}"
a
b

c
$

A common split seperator is the null character. This is similar to string split0 in fish.

$ str="abc\x00def\x00ghi"
$ sep="\x00"
$ arr=(${(ps.$sep.)str})
$ printf '%s\n' $arr
abc
def
ghi
$

Upper/Lower

Convert a string to uppercase with the u modifier. This is similar to string upper in fish.

$ str="AbCdEfGhIjKlMnOpQrStUvWxYz"
$ echo "${str:u}"
ABCDEFGHIJKLMNOPQRSTUVWXYZ
$

Convert a string to lowercase with the l modifier. This is similar to string lower in fish.

$ str="AbCdEfGhIjKlMnOpQrStUvWxYz"
$ echo "${str:l}"
abcdefghijklmnopqrstuvwxyz
$

Match/Replace

The zsh/pcre module allows you to match strings in Zsh.

$ zmodload zsh/pcre
$ str="The following are zip codes: 78884 90210 99513"
$ setopt REMATCH_PCRE
$ [[ $str =~ '\d{5}' ]] && echo "contains zips" || echo "no zips"
contains zips
$
$ zmodload zsh/pcre
$ str="https://gist.github.com/mattmc3/110eca74a876154c842423471b8e5cbb"
$ pattern='^(ftp|https?)://'
$ pcre_compile -smx $pattern
$ pcre_match -b -- $str
$ [[ $? -eq 0 ]] && echo "match: $MATCH, position: $ZPCRE_OP" || echo "no match"
match: https://, position: 0 8
$

Replacing leverages the s modifier. 'g' Means globally.

$ url=https://github.com/zsh-users/zsh-autosuggestions.git
$ url=${url%.git}
$ url=${url:gs/\@/-AT-}
$ url=${url:gs/\:/-COLON-}
$ url=${url:gs/\//-SLASH-}
$ echo $url
https-COLON--SLASH--SLASH-github.com-SLASH-zsh-users-SLASH-zsh-autosuggestions
$

Collect

There are lots of different ways strings need collected. Sometimes you have a string with embedded newlines that you need to split into an array, preserving blanks.

$ str=$(printf '%s\n' four score '' "&" '' seven years ago)
$ echo ${(q-)str}
'four
score

&

seven
years
ago'
$ # remove blanks
$ arr=(${(f@)str})
$ echo $#arr
6
$ # preserve blanks
$ arr=("${(f@)str}")
$ echo $#arr
8
$

Tests

This file passes clitests:

zsh -f -- =clitest --list-run --progress dot --color always zsh-strings.md
@allenmonroesmith
Copy link

A common split seperator is the null character. This is similar to string split0 in fish.

$ str="abc\x00def\x00ghi"
$ sep="\x00"
$ arr=(${(ps.$sep.)str})

In zsh, you must use $'' instead of "" to embed null characters in a string literal; see 5.1.3 POSIX quotes. The double quotes shown above will literally include the four characters "\x00" in the string.

You can also put the \0 directly in the expansion flag, or use the short form (0).

str=$'abc\0def\0ghi'
sep=$'\0'
# These all do the same thing:
arr=(${(ps:$sep:)str})
arr=(${(ps:\0:)str})
arr=(${(0)str})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment