0

I am trying to read members of the PATH into an array using zsh 5.8. I am used to Bash, where the following normally works:

IFS=: p=($PATH)

However with zsh, this actually gives me an array with only one item. If I write instead:

IFS=: p=($(echo $PATH))

then I get the correct number of elements. Why is that?

2
  • If you're looking for $PATH as an array, you can use $path in zsh, see zsh.sourceforge.io/Doc/Release/Parameters.html (search for "path <S> <Z> (PATH <S>)").
    – Wieland
    Aug 9, 2021 at 17:17
  • @Wieland Thanks a lot for the tip! I'm still curious about the difference between bash and zsh in the specific example I posted, regardless of using the $PATH variable or something else like foo='a:b c:d' (should yield 3 items); if you feel like writing an answer, that would be great :)
    – Jonathan H
    Aug 9, 2021 at 17:23

1 Answer 1

4

Because zsh doesn't do word splitting to parameter expansions by default, unlike POSIXy shells. It does for command substitutions, so the round trip via one "fixes" it for you. But it's still far from the right thing to do in zsh, and the combination of echo and command substitution could even mangle the data in some cases.

In zsh, you could use $=PATH to ask for word splitting, or use the more proper solutions presented here: How to split a string by ':' character in bash/zsh?

Also in case of PATH, it's tied to the array variable path in zsh by default, so you don't need to do anything to access it as an array.


(In IFS=: p=($PATH), the unquoted expansion would also be subject to filename generation or globbing, so if any of the paths in PATH happen to contain globbing characters (*?[], maybe others), the results may not be what you want. Round-tripping via a command substitution and echo would also remove any trailing newlines, and (depending on echo) might also mangle backslashes.)

7
  • Stephane's answer in the link you shared is pretty scary stuff.. but thanks for the link and for the answer!
    – Jonathan H
    Aug 9, 2021 at 17:30
  • 2
    @JonathanH, mmm. Zsh can be a bit... hard to read with all the one-character modifiers. But that parts=(${(s/:/)str}) is pretty clear still. Within the array assignment we have ${(s/:/)str} which is just a parameter expansion like ${str} or $str, but with the s/:/ modifier that tells it to split on : (s for split, I suppose, and the /s are just separators, parens around to mark it as a modifier). It's cleaner to give the separator there, where it's needed, since changes to IFS could affect the rest of the script too.
    – ilkkachu
    Aug 9, 2021 at 17:37
  • Thanks for the explanation, I guess I scare easily! Still, this looks like voodoo to me: eval $3'=("${(@ps:$2:)1}")'
    – Jonathan H
    Aug 9, 2021 at 17:48
  • 1
    @JonathanH, yes, he's going all in on being generic there. You'd only need that eval trick if you want to be able to put the elements in an array variable of an arbitrary name.
    – ilkkachu
    Aug 9, 2021 at 17:55
  • 1
    @JonathanH, you may happen to be in a special case where the bugs in that incorrect code are not hit, but that doesn't make it any less wrong. You wouldn't want to use that code in a script, and if you advertise that code in an answer here, that should come along with a if you can guarantee the variable doesn't contain wildcard characters and doesn't end in an empty element. Or use the correct a=("${(@s[:])s}") (or typeset -T) in zsh or IFS=:; set -o noglob; a=($s'') in bash/POSIX. Aug 10, 2021 at 5:01

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.