Skip to content

Instantly share code, notes, and snippets.

@mattmc3
Last active October 15, 2025 19:50
Show Gist options
  • Save mattmc3/76ad634f362b5a9a54f1779a4737d5ae to your computer and use it in GitHub Desktop.
Save mattmc3/76ad634f362b5a9a54f1779a4737d5ae to your computer and use it in GitHub Desktop.
ZSH - split string into array

There are a ton of different ways to split up a string in Zsh. This gist attempts to show them with examples to help you build your own. I write Zsh scripts all the time, and still reference back to this gist, so there you go.

From the Zsh docs on Parameter Expansion Flags (yeah - I know... how would anyone ever find that if they didn't know where to look!?)

j:string: Join the words of arrays together using string as a separator.
s:string: Force field splitting at the separator string.

You can also read more by running man zshexpn. (Again, I know, right!? How would anyone know to look there!?)

Example splitting a string on slash character in Zsh.

$ str=part1/part2/part3
$ parts=(${(@s:/:)str})
$ echo $parts
part1 part2 part3
$ echo ${#parts[@]}
3

You can use split ${(@s:/:)str} and indexing [start,end] to do more sophisticated surgery on string parts. Note that Zsh array indexing starts a 1, not 0.

If you need to reassemble, simply join back on the same separator ${(@j:/:)str}. Note: weirdly, the colons can be swapped out for other symbols, so if you prefer periods for example, this would also work: ${(@j./.)str}. Since I'm splitting on slashes, I choose not to use slash as my symbol.

$ url="https://github.com/sorin-ionescu/prezto/blob/master/modules/history/init.zsh"
$ repo="${(@j:/:)${(@s:/:)url}[4,5]}"
$ echo $repo
sorin-ionescu/prezto

An example from the Zsh docs which shows splitting. Note the use of slash below unlike the colon above to surround the split character - remember that symbol is swapable and doesn't change the behavior of the split at all:

$ foo=(ax1 bx1)
$ print -l -- ${(s/x/)foo}
a
1 b
1

Getting the first 2 parts

$ str=a/b/c/d/e/f
$ parts=(${(@s:/:)str})
$ echo ${(@j:/:)parts[1,2]}
a/b

You can also use # and % parameter expansion symbols: see docs. # removes from the left side, and % from the right, which I remember by the fact that # is to the left of % on your actual keyboard. ## and %% use the longest match, while # and % use the shortest.

$ str=part1/part2/part3
$ echo ${str%%/*}
part1
$ echo ${str%/*}
part1/part2
$ echo ${${str%/*}#*/}
part2
$ echo ${str#*/}
part2/part3
$ echo ${str##*/}
part3

Word splitting is done with the '=' character:

$ sentence="ls -l -A -h"
$ arr=(${=sentence})
$ print -l -- $arr
ls
-l
-A
-h
@j13ag0
Copy link

j13ag0 commented Feb 12, 2023

Very useful. Thanks -jg-

@YetAnotherAlexWithBadMemory

Thank! You've saved my 2-3 days!

@kehali-woldemichael
Copy link

Thank you so much! Very helpful!!

@KiLLeRRaT
Copy link

Any idea how I can split on multiple chars? I'm after splitting a string into words how vim would do it, e.g. alpha numeric and underscores count as words, slashes, spaces, etc count as word boundaries.

@mattmc3
Copy link
Author

mattmc3 commented Apr 29, 2025

@KiLLeRRaT - if you need to split on multiple chars, at that point you’re into regex splitting. Something like this:

str="foo::bar//baz"
parts=()

while [[ $str =~ "^(.*?)(::|//|$)(.*)" ]]; do
  parts+=("${match[1]}")
  str="${match[3]}"
  [[ -z $match[2] ]] && break
done

# Output the result
echo "${parts[@]}"     # foo bar baz
echo "${#parts[@]}"    # 3

@KiLLeRRaT
Copy link

Thanks a lot for that, I will give this a shot!

@twalker314
Copy link

twalker314 commented Sep 18, 2025

I eventually arrived here because I originally found bash-specific suggestions that did not work in zsh, for example:

sentence="ls -l -A -h"
read -a arr <<< "$sentence"
for i in ${arr[@]}; do echo "$i"; done
sentence="ls -l -A -h"
readarray arr <<< "$sentence"
for i in ${arr[@]}; do echo "$i"; done

…the latter being available in bash starting with version 4 or later only.

Word splitting is done with the '=' character:

$ sentence="ls -l -A -h"
$ arr=(${=sentence})
$ print -l -- $arr
ls
-l
-A
-h

This was what I was looking for, thanks 🙂 (I almost missed this as it was listed last 😬)

It's comparable to the ZSH equivalent of the first bash solution above:

sentence="ls -l -A -h"
read -A arr <<< "$sentence"
print -l -- $arr

It might be worth mentioning that (in both cases) the delimiter(s) used to split the words are controlled by the IFS environment variable? This may be a simpler solution for this case:

Any idea how I can split on multiple chars? I'm after splitting a string into words how vim would do it, e.g. alpha numeric and underscores count as words, slashes, spaces, etc count as word boundaries.

…than:

if you need to split on multiple chars, at that point you’re into regex splitting. Something like this:

str="foo::bar//baz"
parts=()

while [[ $str =~ "^(.*?)(::|//|$)(.*)" ]]; do
  parts+=("${match[1]}")
  str="${match[3]}"
  [[ -z $match[2] ]] && break
done

# Output the result
echo "${parts[@]}"     # foo bar baz
echo "${#parts[@]}"    # 3

Also, I was ideally looking for a solution that works in both bash and zsh, and settled for this:

sentence="ls -l -A -h"
arr=($(printf "$sentence"))
for i in "${arr[@]}"; do echo "$i"; done

Using printf rather than echo because:

  • zsh's echo builtin processes escape sequences unless given the -R flag
  • bash's echo builtin does not support the -R flag
  • echo -e would probably work the same in both zsh and bash?

Edit: I misread the zsh builtin documentation, it's print that accepts -R, not echo. And both bash's and zsh's echo look like they will accept -E so a similar solution could be:

sentence="ls -l -A -h"
arr=($(echo -E "$sentence"))
for i in "${arr[@]}"; do echo "$i"; done

…with a choice between printf/ echo -e vs. echo -E depending on whether you want to interpret escape sequences or not.

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