Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

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.

files=(`ls`)
for f in $files; do
    print $f
done

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

share|improve this question

2 Answers 2

up vote 9 down vote accepted

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
wibble

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.

files=("${(@f)$(ls)}")

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.

share|improve this answer
    
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 '12 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 '12 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. –  Gilles Nov 26 '12 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 '12 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. –  Gilles Nov 28 '12 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}
done
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.