Python subprocess、mysqldump与管道

5 投票
3 回答
11074 浏览
提问于 2025-04-16 03:29

我遇到了一个问题,想写一个简单的备份和升级数据库的脚本。

错误出现在使用子进程调用mysqldump的时候:

cmdL = ["mysqldump", "--user=" + db_user, "--password=" + db_pass, domaindb + "|", "gzip", ">", databases_path + "/" + domaindb + ".sql.gz"]
print "%s: backup database %s \n\t[%s]" % (domain, domaindb, ' '.join(cmdL))
total_log.write("%s: backup database %s \n\t[%s] \n" % (domain, domaindb, ' '.join(cmdL)))
p = subprocess.Popen(cmdL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

在这段代码之前,我把 sys.stdoutsys.stderr 重定向到文件,这样可以有一个日志系统。

在这些日志中,我发现了错误:

[mysqldump --user=xxxxxx --password=yyyyyyyy database_name | gzip > /home/drush-backup/2010-08-30.15.37/db/database_name.sql]

[错误]: mysqldump: 找不到表: "|"

看起来 | 这个字符被当作mysqldump的参数,而不是管道符。

查阅了Python的子进程文档,这种情况是正常的,但我该如何得到我想要的结果(执行命令 mysqldump --user=xxxxxx --password=yyyyyyyy database_name | gzip > /home/drush-backup/2010-08-30.15.37/db/database_name.sql)呢?

编辑 我刚在Python文档中看到这个例子:

output=`dmesg | grep hda`
==>
p1 = Popen(["dmesg"], stdout=PIPE)
p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
output = p2.communicate()[0]

于是我修改了我的脚本:

command = ["mysqldump", "--user=" + db_user, "--password=" + db_pass, domaindb, "|", "gzip", ">", databases_path + "/" + domaindb + ".sql.gz"]
cmdL1 = ["mysqldump", "--user=" + db_user, "--password=" + db_pass, domaindb]
cmdL2 = ["gzip", ">", databases_path + "/" + domaindb + ".sql.gz"]

print "%s: backup database %s \n\t[%s]" % (domain, domaindb, ' '.join(command))
total_log.write("%s: backup database %s \n\t[%s] \n" % (domain, domaindb, ' '.join(command)))

p1 = subprocess.Popen(cmdL1, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
p2 = subprocess.Popen(cmdL2, stdin=p1.stdout, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
cmdError, cmdData = p2.communicate()

现在命令变量只是为了方便记录日志。

这向前迈了一步,但在 > 流的时候又停下来了,出现了这个错误:

[Error]: gzip: >: No such file or directory
gzip: /path/to/backups/dir/natabase_name.sql.gz: No such file or directory

显然,如果我在终端中尝试这个命令,它是可以工作的。

3 个回答

2

试试用 subprocess.Popen(' '.join(cmdL), shell=True) 这个方法。

在这里,管道(也就是把一个命令的输出直接作为另一个命令的输入)和重定向(把输出导向文件等)都是由shell来处理的。默认情况下(在Unix系统上),subprocess 不会使用shell,因为这样会比较慢,而且控制权会少一些。如果你真的需要用到管道或重定向,就必须明确告诉它使用shell。

通常情况下,我们会尽量避免使用管道(所以也就避免使用 shell=True 以及相关的问题),尽量在Python里完成这些操作(比如在你的例子中,可以使用Python标准库里的 gzip 模块)。当然,这样做的时候要小心,把标准输出(stdout,后面要处理的输出)和错误输出(stderr)分开,分别用两个管道处理。

5

给定路径、用户名、密码和数据库名后,下面的代码运行得非常顺利:

import gzip
from subprocess import Popen, PIPE

cmd = "mysqldump --user={user} --password={pswd} {dbname}".format(**locals())        
p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
with gzip.open(path, "wb") as f:
    f.writelines(p.stdout)

subprocess.Popen()中使用f作为标准输出参数也可以,但这样做不会压缩数据。在Python 2.7之前,with语句是不能用的,所以要使用f=gzip.open(..)来打开文件,并在最后用f.close()来关闭它。如果出现错误,可以通过p.stderr.read()来读取错误信息,如果这个信息不是空字符串,最好抛出一个异常来处理。


恢复备份,你可以这样做:

cmd = "mysql --user={user} --password={pswd} {dbname}".format(**locals())
p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)
with gzip.open(path, "rb") as f:
    p.stdin.write(f.read())
    p.communicate()[0]
    p.stdin.close()
    p_err = p.stderr.read()
if p_err:
    raise Exception('Error restoring database:\n{0}'.format(p_err))
4

我不太确定管道会怎么被解释。如果这有问题,你可以通过编程的方式来创建一个管道。

来源于: http://docs.python.org/library/subprocess.html#replacing-shell-pipeline

p1 = Popen(["dmesg"], stdout=PIPE)
p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
output = p2.communicate()[0]

编辑

关于文件重定向,你可以把标准输出(stdout)指向一个文件。

stdin、stdout 和 stderr 分别代表执行程序的标准输入、标准输出和标准错误的文件句柄。有效的值包括 PIPE、一个已存在的文件描述符(一个正整数)、一个已存在的文件对象,以及 None。

例子:

out_file = open(out_filename, "wb")
gzip_proc = subprocess.Popen("gzip", stdout=out_file)
gzip_proc.communicate()

或者如果你听从 Alex 的建议,使用 Python 的标准库 gzip 模块,你可以这样做:

import gzip
import subprocess

...
#out_filename = path to gzip file

cmdL1 = ["mysqldump", "--user=" + db_user, "--password=" + db_pass, domaindb]
p1 = subprocess.Popen(cmdL1, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
dump_output = p1.communicate()[0]

f = gzip.open(out_filename, "wb")
f.write(dump_output)
f.close()

撰写回答