在 Mac 上使用 launchd 自动运行 shell 脚本

Eri*_*cus 7 macos bash restart sh launchd

我正在尝试让脚本每天同时运行以重新启动 Mac。我无法使用节能首选项面板中的内置计划功能,因为我正在运行阻止正常重启的应用程序。如果我通过终端中的命令启动重新启动,shutdown它将强制重新启动,无论哪些应用程序可能会阻止它。

我对 shell 脚本和 launchd 非常陌生,所以我真的是一个新手级别,所以请记住这一点。这是我在这里发表的第一篇文章,但多年来我定期访问并通常找到我正在寻找的答案。

在尝试了各种事情之后,我遇到了一些障碍。我所在的位置是 /Library/LaunchDaemons 文件夹中有一个 .plist 文件,该文件指向应在特定时间运行的脚本。我最初尝试使用在线教程中找到的代码在 bbedit 中编写 .plist 文件。它总是无法加载到 launchctl (手动或通过重新启动)。然后我尝试使用 Xcode 中的 plist 编辑器,效果似乎更好。

完成此操作后,plist 将加载到 launchctl 中。它似乎还尝试在指定时间运行脚本。我查看了控制台,Mac 似乎确实尝试运行该脚本,但失败并出现以下错误:

com.apple.xpc.launchd[1] (com.apple.restart.sh[940]): 服务退出并出现异常代码: 1

我尝试直接从终端运行脚本,例如sh scriptname,Mac 重新启动。

这是脚本:

#!/bin/bash
shutdown -r now
Run Code Online (Sandbox Code Playgroud)

我将脚本保存在 /Library/Scripts 文件夹中。

这是 .plist 的内容:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.apple.restart.sh</string>
    <key>Program</key>
    <string>/Library/Scripts/com.apple.restart.sh</string>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>9</integer>
        <key>Minute</key>
        <integer>50</integer>
    </dict>
</dict>
</plist>
Run Code Online (Sandbox Code Playgroud)

我现在真的不知道出了什么问题,因为 .plist 似乎已成功加载到 launchctl 中,并且脚本尝试在指定时间运行但失败了。如果手动运行该脚本,它就可以工作。

我最好的猜测是这与权限或所有权有关,但这只是我的直觉。我尝试过chown root:wheel在文件上运行。这将错误更改为:

异常代码78

任何帮助将不胜感激。

编辑来自/sf/users/198563501/的以下请求

最新的plist代码在这里:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.erithacus.restart</string>
    <key>Program</key>
    <string>/Library/Scripts/com.erithacus.restart.sh</string>
    <key>StandardOutPath</key>
    <string>/tmp/com.erithacus.restart.stdout</string>
    <key>StandardErrorPath</key>
    <string>/tmp/com.erithacus.restart.stderr</string>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>11</integer>
        <key>Minute</key>
        <integer>10</integer>
    </dict>
</dict>
</plist>
Run Code Online (Sandbox Code Playgroud)

最新脚本内容:

#!/bin/bash

/sbin/shutdown -r now >>/tmp/shutdown.log 2&>1
Run Code Online (Sandbox Code Playgroud)

.stdout 文件的内容为空。

.stderr的内容如下:/Library/Scripts/com.erithacus.restart.sh: line 3: 1: Read-only file system

这是否与 macOS Catalina 的操作系统位于只读分区有关?

编辑2.一个解决方案。

最终的 plist 文件 (com.erithacus.restart.sh) 内容(但必须使用 Xcode 制作):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.erithacus.restart</string>
    <key>Program</key>
    <string>/Library/Scripts/com.erithacus.restart.sh</string>
    <key>StandardOutPath</key>
    <string>/tmp/com.erithacus.restart.stdout</string>
    <key>StandardErrorPath</key>
    <string>/tmp/com.erithacus.restart.stderr</string>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>12</integer>
        <key>Minute</key>
        <integer>10</integer>
    </dict>
</dict>
</plist>
Run Code Online (Sandbox Code Playgroud)

(显然改变下面的时间StartCalendarInterval以适应)

此 plist 保存在 /Library/LaunchDaemons 中

最终脚本(com.erithacus.restart.sh):

#!/bin/bash

date +"%F %T Shutting down"

/sbin/shutdown -r now
Run Code Online (Sandbox Code Playgroud)

脚本保存在/Library/Scripts中

我认为与原始版本相比最关键的根本改变是我将 plist 文件的所有权更改为root使用sudo chown root [plist file name].

然后我必须将其加载为launchctlroot,即sudo launchctl load [plist file name]。奇怪的是,当以这种方式加载时,它不会显示在命令中launchctl list,但会显示 if sudo launchctl list

这导致系统成功定时重新启动计算机,从而覆盖任何可能阻止重新启动的应用程序。这对于我想每天早上 5 点重新启动的服务器很有帮助(所以显然 plist 文件中的时间现在将更改为在那个时间运行)。

特别感谢@marksetcell,他的错误记录建议帮助我解决了这个问题。

Mar*_*ell 3

有几件事需要检查。


首先,确保您的脚本可以执行:

chmod +x /Library/Scripts/com.apple.restart.sh
Run Code Online (Sandbox Code Playgroud)

其次,将完整路径放入shutdown脚本中以确保可以找到它:

#!/bin/bash
/sbin/shutdown -r now
Run Code Online (Sandbox Code Playgroud)

我发现通过运行:

which shutdown
Run Code Online (Sandbox Code Playgroud)

输出

/sbin/shutdown
Run Code Online (Sandbox Code Playgroud)

第三,确保您的脚本是纯 ASCII 文本,而不是 RTF、PagesMS-Word文档:

file /Library/Scripts/com.apple.restart.sh
Run Code Online (Sandbox Code Playgroud)

输出

/Library/Scripts/com.apple.restart.sh: Bourne-Again shell script text executable, ASCII text
Run Code Online (Sandbox Code Playgroud)

第四,在外部测试您的脚本launchctl以确保它首先独立运行:

/Library/Scripts/com.apple.restart.sh
Run Code Online (Sandbox Code Playgroud)

第五,在文件中像这样重定向输出plist

<key>StandardOutPath</key>
<string>/tmp/com.apple.restart.stdout</string>
<key>StandardErrorPath</key>
<string>/tmp/com.apple.restart.stderr</string>
Run Code Online (Sandbox Code Playgroud)