0

I have a bash script that sources a file, which sources another:

script.sh:
cd /script/dir
source funcs.sh

funcs.sh: ... source mode.sh

Now this script runs OK when I run it from command line, from within /script/dir. But when I run it from crontab, the funcs.sh file can't find mode.sh. Probably because for some reason the cd /script/dir doesn't get passed on to it? Is there anything I can do from within script, or do I need to cd in the cron or something similar?

Gnudiff
  • 975
  • How do you execute the script from your crontab schedule? Does the script not have a #!-line? Depending on what shell is running the script (and how it's running it), the source command may or may not search the current directory for the dot-script. – Kusalananda Feb 28 '23 at 08:15

2 Answers2

4

Possible problems:

  1. There is no shebang inside script.sh. If cron is told to run script.sh then it will run like /bin/sh -c script.sh (unless SHELL inside your crontab points to something else than /bin/sh, we don't know this). Since there is no shebang, this applies: Which shell interpreter runs a script with no shebang? The answer depends on what shell your cron uses, we don't know this for sure. Ultimately sh or something else may be chosen as the interpreter of the script. Even if sh is chosen, it may be provided by dash, bash or something else (in Ubuntu dash by default, but we don't know if it's really so in your case).

    The point is we don't really know what the interpreter of script.sh is, when the script is started from cron. I guess in some circumstances it may be the script cannot start from cron at all.

    OTOH when you run the script from your interactive shell, it's possible a different interpreter is chosen and then it works.

  2. cd /script/dir may fail for some reason (example).

  3. source is an alias for ., but not in all shells. Since we don't know the interpreter, we don't know if it understands source.

  4. Even if the interpreter understands source as ., the argument is funcs.sh and it does not contain /. In such case the POSIX behavior of . is it searches for the file (funcs.sh in your case) in directories in $PATH. Now:

    • Even if your $PATH contains . (i.e. the current working directory), the same variable in cron probably does not contain it.

    • Only if search in $PATH fails, some shells try the current working directory.

To fix, do the following:

  1. Use a shebang inside script.sh, so you are in control of what shell will be used to interpret script.sh when executed.

    (Depending on the shell you choose, the fixes (3) and (4) may or may not be necessary; they won't do harm, so if there is any doubt, apply them anyway.)

  2. Abort if cd fails. If the script continues after cd fails then the script may try to source funcs.sh from a wrong directory.

  3. Use . instead of source. The former is portable, the latter is not.

  4. Use an explicit path containing / (in this case ./funcs.sh instead of funcs.sh), so there is no search in $PATH and it's clear what file you mean.

The fixed script.sh will be like:

#!/bin/sh
cd /script/dir || exit 1
. ./funcs.sh

Inside funcs.sh a shebang is not needed because this file is sourced by the shell interpreting script.sh, but fixes (3) and (4) may still be necessary (depending on the chosen shell).

1

Every process has a $PWD (working directory) cron does not run in your $HOME. Instead of cding in your script you can use this

#!/bin/bash

dir="$(dirname "$(readlink -f "$0")")" echo $dir

$dir is the location directory of a script. For example

$ cd /
$ bash /home/junaga/script.sh

outputs /home/junaga because that is the script location

Junaga
  • 379
  • 2
  • 16