目录

为什么systemd中用sh执行source命令有问题

bash的POSIX模式

问题

尝试在一个systemd的service里以sh执行一个脚本,脚本中包含了一条source test1.sh的命令

但是在启动service时,却报出了source: test1.sh: file not found的错误(注意是文件不存在,而不是命令),而且在source执行报错后,脚本就退出不执行了。

我已经在service里指定了WorkingDirectroy变量,并且test1.sh文件的路径也没有问题,所以排除路径的问题。

而我尝试在shell上同样以sh命令执行这个脚本,却发现可以正常执行。

如果我把service里的sh改为bash,也可以正常执行。

如果我将脚本中的source改成source ./test1.sh,也可以正常执行。

所以,能得出以下结论:

  1. systemd里bashsh的执行有差异
  2. sh在systemd和本地shell里执行有差异
  3. source的文件是否带有路径会有差异

探究

首先,我将service里的命令修改为bash,就可以正常执行,那是否因为是两个shell的原因?

但实际上,现在绝大多数的操作系统,都已经将/bin/sh链接到bash了,所以他们执行的其实都是bash

先去man bash里看看source这个命令

1
2
3
4
    If filename does not
    contain a slash, filenames in PATH are used to find the
    directory containing filename, but filename does not need
    to be executable. 

如果文件名不带有"/",那将在PATH里寻找文件

1
2
    When bash is not in posix mode, it searches
    the current directory if no file is found in PATH.

在非posix模式下,最后会搜索当前目录查找文件

那问题可能是在这个差异上。

Bash如果以sh命令执行,将进入POSIX模式

1
2
    When invoked as 'sh', Bash enters POSIX mode after reading the startup
files.

也就是我在service里指定的,其实是以POSIX模式执行的bash

那如果是posix模式,会如何搜索,看下bash这两种模式的差异

1
2
    43. The '.' and 'source' builtins do not search the current directory
     for the filename argument if it is not found by searching 'PATH'.

在posix模式下,source.命令不会搜索当前目录来查找文件,而是只在PATH里搜索

那可以得出结论,以POSIX模式执行的bash,内置的source命令不会在当前目录下搜索一个不带"/“的文件名,它只能去PATH里搜索。

那为什么在正常shell中以sh执行,不会有问题?

之前参考了在shell和systemd中执行sh命令的区别,我猜测是因为non-interactive的原因 但是我又仔细看了关于source命令的搜索规则,没有发现与是否是交互式shell有关的信息 那就来比较了一下两种情况的shell参数:打印一下set 发现了以下区别:

1
2
3
4
# shell下(交互式)
PATH=/opt/jdk1.8.0_151/bin:/opt/jdk1.8.0_151/jre/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:
# systemd下(非交互式)
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin

交互式shell的PATH变量在结尾多了一个空值(null),而在bash里对这个值的解释如下:

1
2
3
4
    A zero-length (null) directory
    name in the value of PATH indicates the current directory.
    A null directory name may appear as two adjacent colons, or
    as an initial or trailing colon.

这个空值代表当前目录,那这就正好对应了bash在posix模式下source命令的搜索规则,也就解释了为什么我在shell下可以正常执行

我看一下systemd里的环境变量:systemctl show-environment

1
2
LANG=zh_CN.UTF-8
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin

确实是不包含当前目录的,而以sh启动bash的情况,在非交互模式下应该也不会执行其他的初始化,所以这个source命令就无法找到不带路径的文件了。

为什么执行到source失败后脚本就退出了?

还是bash这两种模式的差异

1
2
    24. If a POSIX special builtin returns an error status, a
     non-interactive shell exits.

如何修改

既然知道了原因,那就有对应的办法:

  1. 像之前一样修改成bash执行
  2. 在脚本里修改PATH变量
  3. 在脚本里执行一下systemctl import-environment PATH同步一下,但是这种方式应该会是全局的
  4. 在service里手动指定PATH变量,但这种方式必须写全部的,不能引用$PATH