Skip to content

Instantly share code, notes, and snippets.

@mattmc3
Last active April 1, 2024 22:44
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment