1

I want to loop through files in "/test/my project/abc/*" using zsh script on Mac OSX.

path="/test/my project/abc/*"
for file in ${path}; do
    something
done

will result in white space not being escaped and error occuring.

I also tried

path="/test/my project/abc/*"
for file in "${path}"; do
    something
done

In most other usage this method of escaping would have worked but in the "for ... in ..." case it instead treat the whole "${path}" as a string array and only list the single string.

I am stuck and any help would be appreciated.

cr001
  • 655
  • 4
  • 16

2 Answers2

2

File globbing with a variable expansion is turned off by default in zsh. You have to explicitly request it with ${~...}:

ls 'sub dir'; print

p="sub dir/*"
for f in ${~p}; do
    print "in loop: [$f]"
done

output:

fl1     fl2     fl3

in loop: [sub dir/fl1]
in loop: [sub dir/fl2]
in loop: [sub dir/fl3]

Note that this does not use path as a variable name. path is usually tied to the PATH variable, and is effectively a reserved word in zsh. That may be why you saw different results with your examples; in a default zsh configuration, both the quoted and unquoted forms should simply return the pattern and not the glob results.

More info:

  • zshexpn - this man page has a description of ${~spec}.
  • GLOB_SUBST is described in zshoptions. This is another way to force globbing with a variable expansion , but it's usually not a good idea to set this option.
  • Some notes about path, PATH, and typeset -T: https://stackoverflow.com/a/18077919/9307265
Gairfowl
  • 2,226
  • 6
  • 9
  • I had an error saying "${~assetFiles}: bad substitution". My code has the following four lines by trying the method you described: `projectPath="/Users/adminadmin/Tutorials/New Unity Project"` `assetBundlePath="${projectPath}/AssetBundles/OSX"` `assetFiles="${assetBundlePath}/*"` `for file in ${~assetFiles}; do` using your syntax and the error occured at the last line containing `for`. – cr001 Dec 27 '21 at 03:39
  • @cr001 - it sounds like your shell is `bash`, not `zsh`. macOS will sometimes display a message about the default shell now being `zsh`, but it doesn't actually change the shell for an existing account. – Gairfowl Dec 27 '21 at 04:53
  • Oh I see. Maybe it's because I am using Jenkins to run the script on Mac. I know it becomes kind of off-topic but is there a good way to achieve my purpose using bash then? I will accept your answer as it answered my original question. – cr001 Dec 27 '21 at 04:56
  • One thing to try is what @chepner mentioned above, moving the wildcard into the loop: `for file in "${assetBundlePath}"/*`. Or if you can add a [hashbang](https://en.wikipedia.org/wiki/Shebang_(Unix)) to the script, that can eliminate some of the dependency on the calling environment. – Gairfowl Dec 27 '21 at 05:14
0

First of all, do NOT name your own variable path. This name is a special meaning in zsh, and you may observe all kinds of funny effects if you fiddle with this variable in a different way as was forseen by the zsh designers.

Secondly, since your variable - let's call it pathes - is supposed to represent a set of pathes, it would make more sense to use an array:

pathes=(/test/my project/abc/*)
for file in $pathes[@]
do
  something
done
user1934428
  • 19,864
  • 7
  • 42
  • 87