我有一个流媒体备份脚本,我按如下方式运行:
./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
以信号终止。
答案 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
重点:
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)。