在失败时杀死管道中的下一个命令

时间:2015-09-21 14:56:00

标签: bash

我有一个流媒体备份脚本,我按如下方式运行:

./backup_script.sh | aws s3 cp - s3://bucket/path/to/backup

aws命令将stdin以原子方式传输到云存储。如果进程在没有EOF的情况下中断,则上传将中止。

如果aws以非零退出代码退出,我希望./backup_script.sh进程被终止。

这样做的任何bash技巧?

编辑: 您可以使用以下脚本测试解决方案:

#!/usr/bin/env python
import signal
import sys
import functools

def signal_handler(signame, signum, frame):
    print "Got {}".format(signame)
    sys.exit(0)

signal.signal(signal.SIGTERM, functools.partial(signal_handler, 'TERM'))
signal.signal(signal.SIGINT, functools.partial(signal_handler, 'INT'))

for i in sys.stdin:
    pass

print "Got EOF"

示例:

$ grep --bla | ./sigoreof.py
grep: unrecognized option `--bla'
usage: grep [-abcDEFGHhIiJLlmnOoqRSsUVvwxZ] [-A num] [-B num] [-C[num]]
    [-e pattern] [-f file] [--binary-files=value] [--color=when]
    [--context[=num]] [--directories=action] [--label] [--line-buffered]
    [--null] [pattern] [file ...]
Got EOF

我希望./sigoreof.py以信号终止。

5 个答案:

答案 0 :(得分:4)

如果出现错误,

backup_script.sh 应该具有非零退出状态,因此您的脚本应如下所示:

if ./backup_script.sh > output.txt; then
    aws s3 cp output.txt s3://bucket/path/to/backup
fi
rm -f output.txt

管道在这里并不合适。

如果您确实需要在本地节省磁盘空间,那么您必须反对"上传;如果backup_script.sh发生错误,请删除上传的文件,或上传到临时位置,然后在确定备份成功后将其移至最终路径。

(为简单起见,我忽略了这样一个事实:如果发生错误,让aws自行退出,您可能会上传比您需要更多的部分备份。请参阅{ {3}}以获得更高带宽效率的方法。)

使用

开始备份过程后
mkfifo data
./backup_script.sh > data & writer_pid=$!

使用以下其中一项上传数据。

# Upload and remove if there was an error
aws s3 cp - s3://bucket/path/to/backup < data &

if ! wait $writer_pid; then
    aws s3 rm s3://bucket/path/to/backup
fi

# Upload to a temporary file and move it into place
# once you know the backup succeeded.
aws s3 cp - s3://bucket/path/to/backup.tmp < data &

if wait $writer_pid; then
    aws s3 mv s3://bucket/path/to/backup.tmp s3://bucket/path/to/backup
else
    aws s3 rm s3://bucket/path/to/backup
fi

答案 1 :(得分:3)

采用/更正最初由@Dummy00001提供的解决方案:

mkfifo aws.fifo
exec 3<>aws.fifo # open the FIFO read/write *in the shell itself*
aws s3 cp - s3://bucket/path/to/backup <aws.fifo 3>&- & aws_pid=$!
rm aws.fifo # everyone who needs a handle already has one; can remove the directory entry

if ./backup_script.sh >&3 3>&-; then
    exec 3>&-       # success: close the FIFO and let AWS exit successfully
    wait "$aws_pid"
else
    kill "$aws_pid" # send a SIGTERM...
    wait "$aws_pid" # wait for the process to die...
    exec 3>&-       # only close the write end *after* the process is dead
fi

重点:

  • shell打开FIFO r / w以避免阻塞(打开只写会阻止读取器;这也可以通过在exec打开之前在后台调用reader [即s3命令]来避免写的一面)。
  • FIFO的写入结束由脚本本身保存,因此读取结束永远不会到达文件结尾,直到脚本故意关闭它为止。
  • FIFO写入端的aws命令句柄显式关闭(3<&-),因此它不会保持打开状态(在这种情况下,在父级中完成的exec 3>&-将无法成功允许它完成阅读和退出)。

答案 2 :(得分:2)

使用进程替换而不是命名管道的短脚本将是:

#!/bin/bash

exec 4> >( ./second-process.sh )
./first-process.sh >&4  &
if ! wait $! ; then echo "error in first process" >&2; kill 0; wait; fi

它与fifo非常相似,基本上使用fd作为IPC的信息载体而不是文件名。

两句话:我不确定是否有必要关闭fd 4;我假设在脚本退出时,shell会关闭所有打开的文件。

而且我无法弄清楚如何在过程替换中获得过程的PID(任何人?至少在我的cygwin上通常$!没有工作。)因此我使用了杀死组中的所有进程,这可能是不可取的(但我并不完全确定语义)。

答案 3 :(得分:1)

我认为你需要从第三个进程生成两个进程,并在@tourism提到的帖子中使用Lynch的命名管道方法(在答案的下方);或者直接保持管道,但重新编写backup_script.sh,使其在错误情况下保持活动状态,保持stdout打开。 backup_script.sh必须向调用进程发出错误信号(例如,通过向父进程ID发送SIGUSR),这反过来先杀死aws进程(导致原子中止),然后才返回backup_script .sh,除非因管道破裂而退出。

答案 4 :(得分:0)

我有类似的情况:一个shell脚本包含一个管道,它使用了一个自己的函数,该函数希望能够实现终止。查找并显示文件的简单设计示例:

#!/bin/sh
a() { find . -maxdepth 1 -name "$1" -print -quit | grep . || exit 101; }
a "$1" | cat
echo done

这里,函数a需要能够通过调用exit来实现终止。但是,当通过管道(第3行)调用时,它只会终止自己的(子shell)进程。在示例中,仍会显示done消息。

解决此问题的一种方法是检测子shell中的时间并向父级发送信号:

#!/bin/sh
die() { [[ $$ == $(exec sh -c 'echo $PPID') ]] && exit $1 || kill $$; }
a() { find . -maxdepth 1 -name "$1" -print -quit | grep . || die 101; }
a "$1" | cat
echo done

在子shell中,$$是父级的pid,而构造$(exec sh -c 'echo $PPID')是一种与shell无关的方式来获取子进程的pid。如果使用bash,则可以将其替换为$BASHPID

如果subprocess pid$$不同,则会向父母SIGTERM发送kill $$信号,而不是调用exit

给定的退出状态(101)不会被kill传播,因此脚本退出时的状态为143(128 + 15,其中15是SIGTERM的id)。