仅在 Mac OS X 关机时运行脚本(而不是注销或重启)

18

有没有办法只在关机时运行脚本?

我的意思是,只有当计算机真的关闭到关闭状态时才运行此脚本。 当仅注销或重新启动时,不应运行此脚本。


2
你有解决方案能够在关机和重启时运行吗?在这种情况下,问题是要在你的脚本中检测到重启... - Oleg Sklyar
1
我可以请您选择我的回答为更正确的吗? - freedev
5个回答

28

几天前我在Github上发布了一个可以在Mac OS X启动/关机时执行的配置/脚本

基本上,在Mac OS X上,您可以/应该使用系统范围和每个用户的守护程序/代理配置文件 (plist)与bash脚本文件结合使用。这是一个您可以使用的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>boot.shutdown.script.name</string>

<key>ProgramArguments</key>
<array>
  <string>SCRIPT_PATH/boot-shutdown.sh</string>
</array>

<key>RunAtLoad</key>
<true/>

<key>StandardOutPath</key>
<string>LOG_PATH/boot-shutdown.log</string>

<key>StandardErrorPath</key>
<string>LOG_PATH/boot-shutdown.err</string>

</dict>
</plist>
你可以将此文件放置在/Library/LaunchDaemons中。有许多目录可以放置plist文件,这取决于你的需求、进程权限等因素。

你可以将此文件放入/Library/LaunchDaemons中。有许多目录可供选择,具体取决于您的需求和该进程的权限等因素。

~/Library/LaunchAgents         Per-user agents provided by the user.
/Library/LaunchAgents          Per-user agents provided by the administrator.
/Library/LaunchDaemons         System wide daemons provided by the administrator.
/System/Library/LaunchAgents   Mac OS X Per-user agents.
/System/Library/LaunchDaemons  Mac OS X System wide daemons.

这个脚本boot-shutdown.sh会在每次启动/关机时被加载和执行。

#!/bin/bash
function shutdown()
{

  # INSERT HERE THE COMMAND YOU WANT EXECUTE AT SHUTDOWN OR SERVICE UNLOAD

  exit 0
}

function startup()
{

  # INSERT HERE THE COMMAND YOU WANT EXECUTE AT STARTUP OR SERVICE LOAD

  tail -f /dev/null &
  wait $!
}

trap shutdown SIGTERM
trap shutdown SIGKILL

startup;

然后调用launchctl命令来加载和卸载守护进程/代理。

sudo launchctl load -w /Library/LaunchDaemons/boot-shutdown-script.plist

它在El Capitan上运行良好。但是我的Xcode7在El Capitan上崩溃,然后我回到Yosemite,脚本在Yosemite上无法工作。我必须使用LogoutHook来运行关机函数。 - Anh Bảy
它在El Capitan系统上关机时无法工作,仅适用于开机,我是否遗漏了什么。 - Sangram Shivankar
尝试在此处发布您的问题 https://github.com/freedev/macosx-script-boot-shutdown - freedev
你可能更喜欢使用以下命令代替tail -f /dev/null:mkfifo /tmp/wait-fifo; read < /tmp/wait-fifo我认为tail -f会进行主动轮询,这有点浪费资源。 - user914330

3
看起来最简单的方法是编写一个小型的C++应用程序,作为守护进程运行,使用launchctl捕获关机通知但忽略重启通知(见下文),然后调用作为参数传递给它的任何内容,例如shell脚本。看起来苹果并没有提供在其他语言中捕获这些通知的库。

从苹果的"Kernel Programming"手册https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/KernelProgramming/KernelProgramming.pdf中可以看到,页面150:

"尽管OS X没有传统的BSD风格的关机挂钩,但I/O Kit在最近版本中提供了等效的功能。由于I/O Kit提供了这个功能,所以您必须从C++代码中调用它。"

"要注册通知,您需要调用registerSleepWakeInterest (在IOKit/RootDomain.h中描述)并注册休眠通知。如果系统即将关闭,您的处理程序将收到消息类型kIOMessageSystemWillPowerOff。如果系统即将重新启动,您的处理程序将获得消息类型kIOMessageSystemWillRestart。如果系统即将重新启动,则您的处理程序将获得消息类型kIOMessageSystemWillSleep。"

如您所见,重启消息与关机是不同的,因此您可以专门处理关机情况。


1
这将是实现它的干净方式,但要复杂得多。 - Audio01
两年后,我建议使用Go替代C++,以使事情更简单... - Oleg Sklyar
@olegsklyar,你好,我尝试使用这个驱动程序的注册表,但似乎我的回调函数在ShotDown和Restart事件中没有被调用,也许你也遇到了类似的问题? - Irad K

2
这里有一个可行的方法(我刚刚测试过),但它非常技术性,不适合经验不足的人... 我在/sbin/shutdown周围建立了一个包装器。即使您从GUI中的苹果菜单关闭您的Mac,这也可以工作。
基本上,您需要以root身份su,并将现有的、由Apple提供的shutdown二进制文件重命名为shutdown.orig
su -
cd /sbin
mv shutdown shutdown.orig

接下来,您需要创建一个名为shutdown的Bash脚本,先执行您想要的操作,然后再execs原始的苹果提供的关机二进制文件。

#!/bin/bash
Do something you want done before shutdown
exec /sbin/shutdown.orig "$@"

有三个需要注意的地方...

1. Make all the permissions the same on shutdown as shutdown.orig
2. Parse the parameters to the originl shutdown and see if `-r` is one of them as this means it is a `restart` shutdown. You will also have to pass through the other parameters that Apple calls the script with - if any.
3. Apple may feel at liberty to overwrite your lovely, shiny, new `shutdown` script when updating OSX, so maybe abstract out the bulk of your personal shutdown script into another place so that you can easily re-insert a single-line call to it if/when Apple overwrites it at some point.

注意!首先要备份!


1
这是一种不太规范的方法,如果在一个封闭的“受控”环境中(即没有更新),它应该可以正常工作。 - Audio01
3
@Audi01:脏?脏?我更喜欢“稍微没洗过但有效”的说法 :-) - Mark Setchell
1
当El Capitan发布时,这将不再起作用。shutdown/sbin中,该目录受rootless保护。 - Martijn Bleeker
@TheBleacher 啊哈-所以第三点成真了!谢谢你的更新。你能提供一些链接供大家参考和理解吗? - Mark Setchell
1
很抱歉,目前文档不足。我所能找到的仅有:系统完整性保护(也称为“无根”)是一种新的系统安全功能,可以防止用户或任何进程写入受系统保护的文件夹。此列表包括 /System、/bin、/usr(但不包括 /usr/local)和 /sbin。(来自:http://arstechnica.com/apple/2015/06/preview-os-x-el-capitans-first-beta-is-a-promising-heap-of-refinements/4/) - Martijn Bleeker
@MarkSetchell:这个解决方案是否也适用于其他操作系统,如Linux / Windows? - rose

1
我已经在我的MacOS 12.5.1上测试过了,它确实可以工作!请参考https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CustomLogin.html#//apple_ref/doc/uid/10000172i-SW10-SW1
 cat LogoutHook.sh

#!/bin/bash

export BLUEUTIL_ALLOW_ROOT=1

function shutdown()
{
  echo `date` >> /Users/blanc/.LaunchShell/shutdown_date.log
  echo `whoami` >> /Users/blanc/.LaunchShell/shutdown_date.log
  /usr/local/bin/blueutil -p 0
  sleep 1
  #exit 0
}

shutdown;

sudo defaults write com.apple.loginwindow LogoutHook /Users/blanc/.LaunchShell/LogoutHook.sh

0

如果有人像我一样通过谷歌搜索类似但略有不同的问题来到这个答案。

由于我发现了CopyQ崩溃的错误https://github.com/hluk/CopyQ/issues/1301,我不得不编写一个cron脚本,以便在它崩溃时重新加载。 但是,如果在关闭或重启期间退出copyq,我不想重新加载copyq

最终我使用了

if ps aux | grep "Finder.app" | grep -v grep
then
    echo "I am still running"
else
    echo "I am in shutdown"
fi

为了检测系统是否正在关机,(您可以根据需要使用Chrome.app或任何其他始终运行的应用程序)

虽然不是完美的解决方案,但它帮助我解决了类似的问题。


1
专业提示--无需使用grep -v: ps aux | grep "[F]inder.app" - NorseGaud

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接