Skip to content

Instantly share code, notes, and snippets.

@mRB0
Last active December 23, 2024 19:34
Show Gist options
  • Save mRB0/7ed3b977d7da1a65e4e6cfc5a7e512f4 to your computer and use it in GitHub Desktop.
Save mRB0/7ed3b977d7da1a65e4e6cfc5a7e512f4 to your computer and use it in GitHub Desktop.
zsh: Reference for word splitting behavior with variable and subshell expansion
#!/usr/bin/env zsh
# -*- coding: utf-8 -*-
# The only differences I observed in the behavior of variable
# expansion were around how newlines and spaces are treated ("word
# splitting"). When word splitting is performed, newlines are turned
# into spaces, and consecutive spaces are collapsed into a single one.
#
# I never observed any issue with unicode characters, or special
# characters that are significant to the shell in regular syntax -
# these always passed through unmodified and uninterpreted.
# For debugging: Shows commands being invoked, with actual quoted
# arguments.
set -x
# Output some test data.
cmd() {
cat <<"EOF"
OneWord
Two Words
Many Spaces
Leading spaces!
An 'apostrophe', "quotes", $ sign, (parens), ? mark, \ backslash, \
'An '\''apostrophe'\'', "quotes", $ sign, (parens), ? mark, \ backslash, now escaped for a shell \'
c'est français
🤣
EOF
}
# NO SHELL BEHAVIOR: Store the output as a single scalar string.
# Spaces, newlines, special characters are intact. Either works.
content="$(cmd)"
content=$(cmd)
# NO SHELL BEHAVIOR: Echo the content exactly as it was stored, with
# spaces, newlines, special characters intact. Unlike bash, zsh
# doesn't perform word splitting on expanded variables by default, but
# you can change that with setopt SH_WORD_SPLIT. Either works.
echo "$content"
echo $content
# NO SHELL BEHAVIOR: Echo the content exactly, without storing it in
# a variable.
echo "$(cmd)"
# CAUSES SHELL BEHAVIOR: Perform word splitting: Newlines are treated
# as spaces, consecutive spaces are dropped,
echo $(cmd)
# CAUSES SHELL BEHAVIOR: ${=name} performs word splitting on $name
# even if it's quoted.
echo "${=content}"
# Any text surrounding ${=name} will get "glued" to the first & last
# words in $name after word splitting is performed, even if there are
# spaces in the surrounding text, but otherwise each word in $name
# gets passed as a separate argument.
#
# This split happens even if it looks like we're passing a single
# argument:
food='barbecue basil spam eggs'
print -rl -- "We have ${=food} and nothing else"
# Output:
# We have barbecue
# basil
# spam
# eggs and nothing else
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment