Search code examples
shellcronzsh

Cron stops the script if the file is not found


I have the following simple script:

#!/bin/sh

a() {
  echo 1
}

a

b() {
  for file in "${DOWNLOADS}"123_*; do
    mv "${file}" "${DOWNLOADS}321"
  done
}

b

c() {
  echo 2
}

c

it is executable and if I call it from the terminal it works exactly right: a, b, c. But if I try to execute it via cron and there is no "123_{something}" file in the "${DOWNLOADS}" directory, then only function a is executed, and the beginning of the foor loop. Function c is not called because the script stops.

crontab -l

=>

10 20 * * * zsh /user/file

Debugging showed the following:

10 20 * * * zsh /user/file >> ~/tmp/cron.txt 2>&1

=>

+/user/file:47> a
+a:1> echo 1
1
+/user/file:67> b
file:12: no matches found: /Users/ivan/Downloads/123_*

As can be seen the execution of the script stopped immediately after the file was not found.

I don't understand why the execution of this script via cron stops if the file is not found, and how this can be avoided; can anyone explain this?

Or maybe it's just the limitations of my environment?


Solution

  • I don't understand why the execution of this script via cron stops if the file is not found, and how this can be avoided; can anyone explain this?

    Using globs that don't match anything is an error:

    % print nope*
    zsh: no matches found: nope*
    

    You can fix this by setting the null_glob option:

    % setopt null_glob
    % print nope*
    [no output, as nothing was found]
    

    You can set this for a single pattern by adding (N) to the end:

    % print nope*(N)
    

    So in your example, you end up with something like:

    b() {
      for file in ${DOWNLOADS}123_*(N); do
        mv $file ${DOWNLOADS}321
      done
    }
    

    NOTE: this only applies to zsh; in your script you have #!/bin/sh at the top, but you're running it with zsh. It's best to change that to #!/usr/bin/env zsh if you plan on using zsh.

    In standard /bin/sh, it behaves different: a pattern that doesn't match gets replaced by the pattern itself:

    % sh
    $ echo nope*
    nope*
    

    In that case, you need to check if $file in the loop matches nope* and continue if it does. But you're running zsh so it's not really applicable: just be aware that /bin/sh and zsh behave quite different with the default settings (you can also get the same behaviour in zsh if you want with setopt no_nomatch).