24

In FreeBSD 12, using the zsh shell, I noticed this difference when looking at $path (lowercase) versus $PATH (uppercase).

echo $path

/sbin /bin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin /usr/home/freebsd/bin

echo $PATH

/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/home/freebsd/bin

One output is delimited by SPACE character, the other by COLON character.

➥ Why the difference?

Are these two different, separate variables? Or does the lowercase/uppercase trigger some kind of trick or meaning I do not know about?

Is this a zsh feature? Or a feature of FreeBSD?

5
  • 3
    Aside: In any POSIX-compliant shell, variable names with any lowercase characters are guaranteed safe for application use (not to silently modify shell or system behavior when changed). This is one of the places where zsh's decision to only follow the standard where it makes sense to them can make headaches for script authors, because the standardized guarantees don't apply. Commented Jul 26, 2019 at 11:22
  • @CharlesDuffy do you have a link to the part of the standard where it says any of that about the lower and upper case variables? Thanks.
    – user313992
    Commented Jul 28, 2019 at 9:41
  • @mosvy, pubs.opengroup.org/onlinepubs/9699919799/basedefs/… -- read it keeping in mind that shell and environment variables share a single namespace (setting the a shell variable updates the value of any existing like-named environment variable; setting an environment variable initializes shell variables). The specific lines: The name space of environment variable names containing lowercase letters is reserved for applications. Applications can define any environment variables with names from this name space without modifying the behavior of the standard utilities. Commented Jul 28, 2019 at 14:15
  • @CharlesDuffy That doesn't apply here. Setting path inside zsh will not update any path envvar: path=junk zsh -c 'echo $path; path=garbage; /usr/bin/printenv path'.
    – user313992
    Commented Jul 28, 2019 at 14:23
  • 2
    @mosvy, you've convinced me that it doesn't violate the letter of the standard. The spirit, on the other hand, would make for path in "$dir"/* reflexively safe-to-write code. Commented Jul 28, 2019 at 14:25

1 Answer 1

35

That's a feature of zsh inherited from csh/tcsh.

The $path array variable is tied to the $PATH scalar (string) variable. Any modification on one is reflected in the other.

In zsh (contrary to (t)csh), you can tie other variables beside $PATH with typeset -T. It's conventional, but not mandatory, to use an uppercase name for the colon-separated scalar and the same name in lowercase for the array. While colon is the default separator, other separators can be used (for instance newline to tie a multi-line string to an array, or comma to tie a csv row to an array)

In recent versions of zsh, typeset -p PATH or typeset -p path shows the link between the two variables:

% typeset -p path
typeset -aT PATH path=( /home/chazelas/bin /usr/local/bin /usr/bin /bin )

That's useful in that it makes it easier to add remove components or loop over them.

Doing a typeset -U path to make the elements unique also helps keeping the $PATH variable clean (something similar can be achieved in tcsh with set -f).

For completeness, fish and yash are two other shells that can treat $PATH as an array, though in their case, that's not via a separate lowercase variable.

In fish, variables whose name ends in PATH are treated as lists implicitly split/joined on colons, so set PATH /foo /bar and set PATH /foo:/bar are equivalent there.

In yash, exporting an array to the environment results in the value of the environment variable containing the elements joined with a colon. So you can do PATH=(/foo /bar) there. Note that when importing $PATH from the environment upon start, yash does not automatically create it as an array.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .