2

I am trying to accomplish something that is easy in bash: look for files in a folder and source them if they exist (and do not output if no files exist).

In bourne shells this is how to do it:

  if [ -d /etc/profile.d ]; then
      for f in `ls -1 /etc/profile.d/*.sh 2> /dev/null`; do
          . $f
      done
  fi

I am new to zsh and cannot get the equivalent working. What am I doing wrong?

if [[ -d "/etc/zsh.d" ]]; then
    for file in (ls -1 /etc/zsh.d/*.zsh 2> /dev/null); do
        source $file
    done
fi

fail: parse error near '>'.

I have tried many variations and cannot get it to be as smooth as the sh/bash equivalent. It's as if redirection does not always work within subshells.

3 Answers 3

4

After a later email to the zsh users mailing list, I was given the nearly ideal solution to the problem:

if [[ -d "/etc/zsh.d" ]]; then
  for f in /etc/zsh.d/*.zsh(N); do
    source $f
  done
fi

The (N) tells zsh to set the NULL_GLOB option for that pattern. When no matches are found, the glob expands to an empty string instead of throwing an error. In zsh a for loop over an empty expansion does nothing, which is the behavior we want here.

1
  • 2
    If you add NULL_GLOB than you can remove the wrapping if statement. Even if dir doesn't exist you'll get an empty string. So you just need the for loop actually... Jun 24, 2014 at 9:12
3

Parsing ls is a bad idea.

Instead you can just use shell globbing to get a list of files:

if [[ -d "/etc/zsh.d" ]]; then
  for f in /etc/zsh.d/*.zsh; do
    source $f
  done
fi

As for the redirection, I'm not sure what the purpose of it is in your example, but if you use this method then it's the shell that might throw an error, not ls, so perform the redirection after done in the for loop.

5
  • 1
    The purpose of the redirection is to avoid this error message when no files match. If zsh.d is empty, you're going to see: no matches found: /etc/zsh.d/*.zsh Apr 12, 2013 at 16:39
  • In my particular case, I will control the contents of the this directory, so parsing ls or find isn't all that bad. But I agree that globbing is preferable. Apr 12, 2013 at 17:35
  • Can you edit and add "2> /dev/null" after "done" for me? I tried to edit it, but apparently my edit was rejected. With this addition, the answer ideally solves the problem and I will accept it. Apr 12, 2013 at 21:12
  • If someone rejected an edit they probably deemed it not necessary. In this case, my text below the code says that you can add redirection after done. I can add this as a further example if you like but as you say you're controlling the contents of the directory hopefully you won't get the "no matches" message. Apr 13, 2013 at 7:33
  • I unfortunately have no clue what their justification was, however you do not need to convince anyone else if you edit it. And I'm not going to put useless files in there just to prevent an error message - sometimes this directory is supposed to be empty. Apr 13, 2013 at 8:08
1

I hope you understand that nearly all sh syntax works just fine in zsh?

# Check dir exists then that there are files in it (silently!)
# If there are no .zsh files in /etc/zsh.d then an empty argument
# is passed to for, which then simply skips the loop

if [ -d /etc/zsh.d ]; then
    for file in $(find /etc/zsh.d/ -name '*.zsh'); do
        source $file
    done
fi

I've tried to make it as fast as possible, since you're in a login script.

This should work for the whole Bourne family.

2
  • This is an excellent workaround for this problem. Where did you get endfor from? On my system I had to change endfor to done and then it works perfectly. Apr 12, 2013 at 16:50
  • Yes, sorry about that; with BSD Makefiles .endfor is for ending a for loop; I was just careless there.
    – Chris Rees
    Apr 13, 2013 at 16:34

You must log in to answer this question.

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