当命令正在运行时执行bash循环

31

我想编写一个bash脚本,执行一个命令并同时执行其他操作,如果脚本被终止,要有可能杀死该命令。例如,执行一个大文件的复制,并同时输出自复制开始以来经过的时间,但如果脚本被终止,则还要终止复制。

我不想使用rsync,原因有二:1)速度慢,2)我想学习如何做,这可能有用。

我尝试了以下内容:

until cp SOURCE DEST
do
#evaluates time, stuff, commands, file dimensions, not important now
#and echoes something
done

但它不会执行do - done块,因为它正在等待复制结束。你能否提出建议?


使用 & 启动一个后台作业,并使用 wait - ceving
until 是同步的,不是异步的。对于异步作业,请使用后台运算符 (&)。 - Jo So
抱歉,我不理解“同步”和“异步”。 - marco
4个回答

54

untilwhile 的反义词。它与在另一个命令运行时执行任务无关。为此,您需要使用 & 将您的任务在后台运行。

cp SOURCE DEST &
pid=$!

# If this script is killed, kill the `cp'.
trap "kill $pid 2> /dev/null" EXIT

# While copy is running...
while kill -0 $pid 2> /dev/null; do
    # Do stuff
    ...
    sleep 1
done

# Disable the trap on a normal exit.
trap - EXIT

kill -0 检查进程是否正在运行。请注意,它并不像名称所建议的那样发送信号并杀死进程,至少不是使用信号0。

kill -0 检查进程是否正在运行。请注意,它并不会实际上发送信号并杀死进程,就像名称可能暗示的那样,至少不是使用信号0。


3
很好的回答 - 我不知道 kill -0。将 2> / dev / null 添加到 kill -0 $pid 以抑制一旦进程不存在就发出的错误消息。 - mklement0
很棒的答案!用于创建一个互联网速度测试脚本的活动指示器:https://dev59.com/7Wcs5IYBdhLWcg3w5H5a#42318974 - Victoria Stuart

15
解决您的问题需要三个步骤:
  1. 在后台执行命令,这样它就可以在脚本做其他事情时继续运行。您可以在命令后面加上&来实现此操作。有关详细信息,请参阅Bash参考手册中的作业控制部分

  2. 跟踪该命令的状态,以便您知道它是否仍在运行。您可以使用特殊变量$!来完成此操作,该变量设置为您在后台运行的最后一个命令的PID(进程标识符),如果未启动后台命令,则为空。Linux为每个正在运行的进程创建一个目录/proc/$PID,并在进程退出时删除它,因此您可以检查该目录的存在性以查找后台命令是否仍在运行。您可以从Linux文档项目的文件系统层次结构页面高级Bash脚本指南中了解有关/proc的更多信息。

  3. 如果脚本被终止,则杀死后台命令。您可以使用trap命令来完成此操作,该命令是一个bash内置命令

将这些部分组合在一起:

# Look for the 4 common signals that indicate this script was killed.
# If the background command was started, kill it, too.
trap '[ -z $! ] || kill $!' SIGHUP SIGINT SIGQUIT SIGTERM
cp $SOURCE $DEST &  # Copy the file in the background.
# The /proc directory exists while the command runs.
while [ -e /proc/$! ]; do
    echo -n "."  # Do something while the background command runs.
    sleep 1  # Optional: slow the loop so we don't use up all the dots.
done

请注意,我们检查/proc目录以查找后台命令是否仍在运行,因为如果调用kill -0时进程不存在,则会生成错误。
< p >更新以解释trap的使用:

语法是trap [arg] [sigspec ...],其中sigspec ...是要捕获的信号列表,而arg是在任何这些信号被触发时执行的命令。在本例中,该命令是一个列表:

'[ -z $! ] || kill $!'

这是一种常见的bash惯用法,利用了||的处理方式。形如cmd1 || cmd2的表达式将在cmd1cmd2成功时计算为成功。但是bash很聪明:如果cmd1成功,bash知道整个表达式也必须成功,因此它不会费心去评估cmd2。另一方面,如果cmd1失败,则cmd2的结果决定表达式的总体结果。因此,||的一个重要特点是仅当cmd1失败时,它才会执行cmd2这意味着它是以下(无效)序列的快捷方式:
if cmd1; then
   # do nothing
else
   cmd2
fi

考虑到这一点,我们可以看出:
trap '[ -z $! ] || kill $!' SIGHUP SIGINT SIGQUIT SIGTERM

将测试$!是否为空(这意味着后台任务从未执行)。如果失败,这意味着该任务已经执行,则终止该任务。


1
不错,但是“-e /proc/$!”方法在OS X上不起作用(这可能不是一个问题,但问题不特定于Linux)。如果抑制来自“kill -0”的错误消息是唯一的问题,则只需附加“2>/dev/null”;即:while kill -0 ${!} 2>/dev/null; do - mklement0
感谢您提供的信息和建议! - Adam Liss
谢谢您的回复。您能否解释一下您建议的trap命令的语法?我不理解逻辑或(logical OR)是什么意思。 - marco
啊,抱歉。那是一个常见的bash习惯用法,但第一次看到时会让人感到困惑。已经更新了答案并附上了解释。 - Adam Liss
1
如果您将trap语句移动到启动后台任务的语句之后,则不需要[ -z $! ] ||。此外,由于您没有从陷阱处理程序内部退出脚本,因此while循环可能会运行另一个迭代,这可能是不希望的。最后,即使$!非空(进程可能已终止),kill语句也可能失败;同样,2>/dev/null是我们的好朋友。为了将所有内容组合在一起,我建议将trap处理程序定义如下:trap "kill ${!} 2>/dev/null; exit 3" SIGHUP SIGINT SIGQUIT SIGTERM。并且,只是为了明确:我从您的帖子中学到了东西。 - mklement0
显示剩余2条评论

4

下面是使用ps -p 最简单的方法:

[command_1_to_execute] &
pid=$!

while ps -p $pid &>/dev/null; do
    [command_2_to_be_executed meanwhile command_1 is running]
    sleep 10 
done

这将每10秒运行一次command_2,仅当command_1仍在后台运行时才会运行。
希望这能帮到你 :)

2

您想在shell中同时执行两个任务。通常的方法是使用作业。您可以通过在命令末尾加上&符号来启动后台作业。

copy $SOURCE $DEST & 

你可以使用jobs命令来检查它的状态。
阅读更多:

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