I've been studying zsh scripting for all of 2 hours at this point and I've hit a wall. I want to go through a list of files that may have spaces in them. I'm open to completely different approaches than the following example so long as they're zsh since zsh is what I'm studying, not the task I'm trying to script.

for f in $files; do
    print $f

I'm obviously not just recreating ls, I just want $f to capture the entire file name each iteration of the loop.

2 Answers 2


First, I assume that the use of ls is just an example. You cannot parse the output of ls in any shell, because it is ambiguous. Read Why you shouldn't parse the output of ls(1) if this is news to you. In any shell, to obtain a list of files, use wildcards, e.g. files=(*).

In zsh, like in other shells, the result of command substitution is split into words at whitespace characters (more precisely, according to the value of IFS). (Unlike other shells, the result of command substitution is not subject to globbing in zsh.) So if the output of the ls command is

hello world

then files=($(ls)) sets the files array to contain 3 elements: hello, world and wibble.

If the command substitution is in double quotes, then no splitting is performed. You can perform custom splitting with parameter expansion flags. Use the @ flag to indicate that the result of the splitting is to be an array (oddly, you need to keep the expansion in double quotes, i.e. "${(@)…}", even though the double-quoted string will expand to multiple words). For splitting, use the s flag, e.g. "${(@s:,:)…}" to split at commas; the f flag splits at newlines only.


Note that the proper way to iterate over an array in general is for f in $files[@], as $files strips off empty elements (here, it doesn't matter because the elements won't be empty).

print $f interprets $f as a switch if it begins with a - and expands backslashes in $f. Use print -r -- $f, or print -rn -- $f if you don't want to add a newline after the string.

  • Thank you. Yes, ls was just an example. There was no specific goal in mind at the moment, I just want to know how to quote or escape the list elements from any command that might have individual results with white space in them.
    – Felix
    Jan 16, 2012 at 18:17
  • One thing that I find confusing is why the outer () in your files=("${(@f)$(ls)}") expression is necessary with the @f? And just playing around (f) seems to work fine as long as () appear on the outside.
    – Koobz
    Nov 26, 2012 at 6:05
  • @Koobz 1. The outer (…) are necessary so that files is an array and not a string (which would contain the concatenation of the lines from the command, undoing the splitting done by (f)). 2. With foo=$'one\n\nthree', contrast print -rl ${(f)foo}` and print "${(@f)foo}". The double quotes are needed to retain empty lines, which you won't get from ls but can happen with other commands. Nov 26, 2012 at 12:35
  • Much appreciated. Is there any rhyme or reason to this madness? Even there, what's confusing is why doesn't the outer () just re-split the string on individual words again if the intermediate value is my concatenated string. This looks like string interpolation in ruby or php for example.
    – Koobz
    Nov 28, 2012 at 4:21
  • @Koobz The reason for the special treatment of empty words is historical compatibility with long-forgotten older versions of zsh. The reason for not automatically splitting is that it's a surprising and often not desirable behavior. Automatic splitting is a behavior of the Bourne shell that zsh doesn't emulate unless you tell it to. Nov 28, 2012 at 10:20

In zsh you can use the shell's expansion which does not perform word splitting by default. Try

for f in /path/to/files/*; do
    print ${f}

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.