5

Is there a less laborious way for setting a local version of the $path array than what's shown in the following snippet?

foo () {
    local holdpath
    holdpath=($path)
    local path
    path=($holdpath)
    if ( some_condition ) path=( $PREFIX $path )

    # do stuff
}

I'm referring specifically to the song-and-dance with holdpath...

If instead I define

foo () {
    local path=($path)
    if ( some_condition ) path=( $PREFIX $path )

    # do stuff
}

...the first assignment to path triggers a bad pattern error. Of course, if instead I define

foo () {
    local path
    path=($path)
    if ( some_condition ) path=( $PREFIX $path )

    # do stuff
}

...the first assignment to path makes no difference (i.e. it can be omitted without altering the results); with or without it, $path will be empty.

EDIT:

The following script tests the various suggestions I've gotten so far:

foo_0 () {
  echo ${#path}
  local PATH=$PATH
  echo ${#path}
}

foo_1 () {
  echo ${#path}
  eval "local path; path=(${(q)path})"
  echo ${#path}
}

foo_2 () {
  echo ${#path}
  eval "$(local -p path)"
  echo ${#path}
}

for i ( 0 1 2 ) {
  fn=foo_$i
  echo "# $fn"
  $fn
  echo
}

The output is:

# foo_0
22
22

# foo_1
22
1

# foo_2
22
22

So the outputs for foo_0 and foo_2 are at least consistent with what I'm trying to achieve. Something is not working with foo_1 as given above, but I get rid of the (q) in the assignment to path, i.e.

foo_1 () {
  echo ${#path}
  eval "local path; path=($path)"
  echo ${#path}
}

...then the output agrees with that of foo_0 and foo_2. Unfortunately, even after reading the documentation for the q qualifier a couple of times, I don't quite understand what it's supposed to be doing in the original recipe.

Also, I cannot understand why the following command-line variant of foo_0 differs from the one above:

% (foo_0a () { echo ${#path}; local PATH=$PATH; echo ${#path} }; foo_0a)
22
1

FWIW, the corresponding command-line variants of foo_1 and foo_2 produce the same results as their originals in the script:

% (foo_1a () { echo ${#path}; eval "local path; path=(${(q)path})"; echo ${#path} }; foo_1a)
22
1
% (foo_2a () { echo ${#path}; eval "$(local -p path)"; echo ${#path}; }; foo_2a)
22
22

Also, in all the cases above in which echo ${#path} produces 1 instead of 22 the reason is that the local $path variable contains all the individual paths in a single string, separated by spaces.

3
  • 1
    A note of warning: use holdpath=("$path[@]"). holdpath=($path) removes the empty elements. Jun 26, 2013 at 18:36
  • ++Thanks for the pointer, and for editing the many typos in my original post!
    – kjo
    Jun 26, 2013 at 19:39
  • I can't reproduce your experience with foo_0a; however, in the process of trying, I noticed that if you have a path component which includes a :, then setting PATH=$PATH increases the size of $path, as I suppose you might expect. So my solution is not correct in that case. But I can't explain your result either.
    – rici
    Jun 27, 2013 at 4:23

2 Answers 2

5

Use the scalar PATH form instead of the array path. Making either of these local effectively makes both of them local, so:

foo() {
  local PATH=$PATH
  if ( some_condition ) path=( $PREFIX $path )

  # do stuff
}

(Note that this will not work if some path component has an embedded :.)

Unfortunately, it is not possible to initialize a local array parameter in one statement, making it impossible to initialize a local array parameter using its original value.

1
  • Well, if elements of $path have colons, it's not the only thing that won't work since $PATH cannot contain a directory name with colon: zsh will be able to search in there, but not the other tools, and for instance, if you do path+=(~/:::) to add your weirdly named bin directory, zsh will find executables in there but other tools (think env, xargs...) will look for them in the current directory (3 times) which is dangerous. Jun 27, 2013 at 9:39
3

While for tied arrays, you can use rici's answer, in the general case, you could do:

foo() {
  eval "local array; array=(${(q)array[@]})"
  ...
}

The (q) is to quote the elements of the arrays. For instance, for a value of $PATH like /foo bar:/x$y, "${(q)path[@]}" would expand to /foo\ bar /x\$y. We need to escape those space and dollar characters because that string is passed to eval.

You could also do:

foo() {
  eval "$(local -p array)"
  ...
}

which would work with any type of variable, but that forks an extra process.

3
  • Thanks! I ran into some problems with your first suggestion; please the latest EDIT to my post.
    – kjo
    Jun 26, 2013 at 20:21
  • 1
    @kjo, yes of course, sorry, because ${(q)path} was inside double quotes, that quotes a single argument. My last edit should fix it. Jun 26, 2013 at 21:31
  • 1
    Not sure if that really counts as less convoluted! Jun 26, 2013 at 23:43

You must log in to answer this question.

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