通过进程名称使用Bash检查Mac进程是否正在运行

33

如何在Mac OS X上使用进程名称在Bash脚本中检查进程是否正在运行?

我正在尝试编写一个Bash脚本,如果进程已停止,则重新启动进程,但如果进程仍在运行,则不执行任何操作。


1
我正在将“unix”添加到标签列表中,因为这个问题与Mac无关...(保留“mac”使Mac专用的人也能找到它...) - Brian Postow
顺便提一句,如果你正在 OS X 上创建类似于守护进程的进程,你应该看看 launchd,这是苹果替代了 cron, init, inetd 等工具。它有许多选项可以在各种上下文和时间启动和重新启动进程。请参阅 man launchdman launchd.plist,并在 Google 上查找各种文档和教程。 - Ned Deily
谢谢Ned。我实际上正在使用Lingon作为一个用户友好的接口来启动launchd - Chris Redford
Brian,你说的话有一半是错的。Mac OS X基于BSD,并且在许多涉及正确操作的命令中具有略微不同的语义。虽然我认为Linux标签并不不合适。 - Ludvig A. Norin
我曾经在一个构建系统中为 Mac OS X、OpenBSD、各种 Linux、AIX、Solaris 和 Cygwin 解决过这个问题。最终,我们采用了两阶段锁定并进行临时文件和进程监控,而不仅仅是进程监控(例如只使用 'ps')。虽然这不是你问题的答案,但也可以作为一般性建议。其中一个大问题是,在各个版本的操作系统中(特别是在 Mac OS X 上),ps 命令行参数会发生变化。 - Ludvig A. Norin
10个回答

42

解析这个:

ps aux | grep -v grep | grep -c [-i] $ProcessName

...这可能是你最好的选择。

ps aux 列出了所有当前正在运行的进程,包括被 grep -v grep 所过滤掉的 Bash 脚本自身,该方法由 Jacob(在评论中)提出,并且 grep -c [-i] $ProcessName 返回一个可选的、不区分大小写的整数,表示该进程名称的进程数量,Sebastian 建议使用整数返回。

这是一个简短的脚本,它可以实现你所需的功能:

#!/bin/bash
PROCESS=myapp
number=$(ps aux | grep -v grep | grep -ci $PROCESS)

if [ $number -gt 0 ]
    then
        echo Running;
fi

编辑:我最初在grep中包含了-i标志,以使搜索时不区分大小写。之所以这样做是因为我使用的示例程序是python,而在Mac OS X上运行的程序名为Python。如果您确切地知道应用程序的大小写,那么-i是不必要的。

这种方法的优点是可以随着你的需求进行扩展——例如,如果将来需要确保同时运行五个实例的应用程序,则已经计数。唯一需要注意的是,如果另一个应用程序的命令行中有与您的程序名称相同的内容,则它可能会出现——如果你很聪明(并遇到此类问题),使用正则表达式搜索grep将解决这个问题。

请查阅Darwin man页,了解有关psgrepwc的信息。


19
谢谢。这个方法是可行的,但有一个例外:必须使用 -gt 1 而不是 0,因为 Bash 脚本中的 grep 命令本身也会被计算为 ps aux 中的行之一,至少在我的初步测试中是这样的。 - Chris Redford
13
我喜欢在 wc 前的管道中添加一个 grep -v grep。这样,grep 命令就会被排除在找到的匹配数之外。 - Jacob
3
Jed和Chris,你们可以使用另一个负面的grep来维护-gt 0功能。例如:ps aux | grep -i $ProcessName | grep -v grep | wc -l - morgant
5
不必使用很多命令,grep 提供了你所需的一切:num=$(ps ax | grep -c -i "[Tt]ransmission") - Sebastian Stumpf
使用这种方法是否比使用pgrep(参见@Nick Zalutskiy的答案)有任何优势? - Matthemattics
显示剩余2条评论

38

一个更短的解决方案:

if pgrep $PROCESS_NAME; then
    echo 'Running';
fi

说明:

pgrep 命令如果存在匹配 $PROCESS_NAME 的进程则退出代码为 0,否则退出代码为 1。
if 语句检查 pgrep 的退出代码,对于退出代码来说,0 表示成功。


4
你可能想要使用-x选项,因为默认情况下它匹配子字符串。使用-x选项可以改变这种行为,使其需要进程名称的精确匹配。 - Auke
@Hypermattt 这不是被接受的答案,因为他是在我提出问题4年后才写的。不过这个解决方案很棒。肯定会得到我的赞同。 - Chris Redford
很不幸,如果你在包装器中运行某些东西(例如Python),它就无法工作。Jed Smith的解决方案可能更适用于这种情况。 - Evils

12

另一种方法是使用(滥用?)killall命令的-d选项。 -d选项不会实际杀死进程,而是会打印出将要执行的内容。 如果找到匹配的进程,则它也会以状态码0退出;如果没有找到匹配的进程,则以状态码1退出。结合起来,可以这样做:

#!/bin/bash
`/usr/bin/killall -d "$1" &> /dev/null`
let "RUNNING = ! $?"     # this simply does a boolean 'not' on the return code
echo $RUNNING

为了表扬归功于它的人,我最初是从iTunes安装程序的脚本中提取了这个技巧。


我不会给这个回答点踩,因为我认为只有在回答错误或不合适时才应该使用-1,但是哇。为什么要那样做,当你可以使用ps和grep呢?特别是因为我相信使用ps和grep正是killall所做的... - Brian Postow
4
实际上,killall 命令比 'ps | grep' 命令更为智能。如果你只使用 ps 命令的输出来进行 grep,你可能会得到错误的匹配结果。举个例子,如果你的进程名为“Library”,在 Mac 上执行 'ps waux | grep Library' 命令会匹配很多东西。虽然我承认 killall 这种方法有些古怪,但它只会匹配真正名为“Library”的进程。 - amrox
对不起,您使用的是哪个系统?我在killall命令中没有看到-d选项。http://linux.die.net/man/1/killall - sobi3ch
@sobi3ch:讨论的是Mac OS X上缺少pidof,而不是Linux。在Mac OS X上,-d选项由man killall文档记录。除非有一些事实或法定标准化,否则不同操作系统上具有相同名称的程序之间可能没有太多共性。 - Jonathan Leffler

5
这个简单的命令可以解决问题。进程名周围的方括号会防止grep命令显示在进程列表中。请注意,逗号后面没有空格。但需要注意的是,在某些Unix系统上,ps命令可能需要在选项前加上破折号,这可能会存在一些移植性问题。
ps axo pid,command | grep "[S]kype"

优点是您可以像这样在if语句中使用结果:'
if [[ ! $(ps axo pid,command | grep "[i]Tunes.app") ]]; then
    open -a iTunes
fi

或者,如果你更喜欢这种样式:

[[ ! $(ps axo pid,command | grep "[S]kype") ]] && open -a Skype  || echo "Skype is up"

另一个优点是您可以通过在 awk '{print $1}' 后添加管道来获取 pid。
echo "iTunes pid: $(ps axo pid,command | grep "[i]Tunes.app" | awk '{print $1}')"

4
你可以使用killall或者kill,具体取决于你是通过进程ID还是进程名来查找任务。
通过进程名:
if ! killall -s -0 $PROCESS_NAME >/dev/null 2>&1; then
  # Restart failed app, or do whatever you need to prepare for starting the app.
else
  at -f $0 +30seconds # If you don't have this on cron, you can use /usr/bin/at
fi

按PID方式:

if ! kill -0 $PID 2>/dev/null; then
  # Restart app, do the needful.
else
  at -f $0 +30seconds
fi

如果您查看OSX手册,您会看到一组不同的进程管理命令;由于它不是Linux内核,所以他们管理进程的方式有所不同。 https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/killall.1.html 来自我的终端的示例输出(当然撤销用户和主机名):
user@localhost:~$ kill -0 782 # This was my old, stale SSH Agent.
bash: kill: (782) - No such process
user@localhost:~$ echo $?
1

user@localhost:~$ kill -0 813 # This is my new SSH agent, I only just created.
user@localhost:~$ echo $?
0

从 kill -0 命令的返回码可以安全地判断进程是否在运行,因为 -0 不会发送任何信号给应用程序处理。它不会杀死应用程序,“kill” 之所以被称为 “kill”,只是因为通常用于停止应用程序。
当查看源代码中使用的接口时,您会发现它实际上直接与进程表交互(而不是从已加载的 ps 输出中筛选),并向应用程序发送信号。有些信号表示应用程序应该关闭或停止,而其他信号则告诉它重新启动服务、重新读取配置或重新打开最近轮换的日志文件描述符。"kill" 和 "killall" 可以做许多不终止应用程序的事情,并且通常用于简单地向应用程序发送信号。

2

我没有足够的声誉来评论上面的killall答案,但是有killall -s可以在不发送任何信号的情况下执行:

killall -s "$PROCESSNAME" &> /dev/null
if [ $? -eq 0 ]; then
    echo "$PROCESSNAME is running"
    # if you also need the PID:
    PID=`killall -s "$PROCESSNAME" | awk '{print $3}'`
    echo "it's PID is $PID"
fi

1

当然有!

OpenBSD和Darwin(Mac OS X)的pgrep、pkill和pfind。

http://proctools.sourceforge.net

(也可通过 MacPorts 获取:port info proctools)

pidof 由 nightproductions.net 开发


很棒的东西,但不是标准的。有时候只用标准的东西解决问题更好,有时候则不是! - Ludvig A. Norin

1
我扩展了在网络上找到的pidof脚本,使用正则表达式(通常是子字符串)并且不区分大小写。
#!/bin/sh
ps axc  |awk "BEGIN{ n=tolower(\"$1\")}\
    tolower(\$5) ~n {print  \$1}";

只需创建名为“pidof”的脚本,并使用此内容将其放入您的路径中,即在其中一个目录中。

echo $PATH

并使其可执行(可能需要使用sudo)。
chmod 755 /usr/local/bin/pidof

并且像这样使用它,当然要保留HTML标签。
kill -9 `pidof pyth`

0

也许对于原帖已经太晚了,但这可能会帮助其他发现这个线程的人。

上面提到的amrox主题的以下修改在我的OS X上重新启动应用程序效果很好:

killall -d TextEdit &> /dev/null && killall TextEdit &> /dev/null; open -a TextEdit

我使用以下的AppleScript来更新和重启守护进程:
tell application "System Events" to set pwd to POSIX path of container of (path to me)
do shell script "launchctl unload -w /Library/LaunchDaemons/time-test.plist; cp -f " & quoted form of pwd & "/time-test.plist /Library/LaunchDaemons; launchctl load -w /Library/LaunchDaemons/time-test.plist" with administrator privileges

它假设原始或更新的plist文件与AppleScript位于同一目录中。


0

Mac 有 pidof 命令吗?...

if pidof $processname >/dev/null ; then echo $processname is running ; fi

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