Python subprocess、mysqldump与管道
我遇到了一个问题,想写一个简单的备份和升级数据库的脚本。
错误出现在使用子进程调用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.stdout
和 sys.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 个回答
试试用 subprocess.Popen(' '.join(cmdL), shell=True)
这个方法。
在这里,管道(也就是把一个命令的输出直接作为另一个命令的输入)和重定向(把输出导向文件等)都是由shell来处理的。默认情况下(在Unix系统上),subprocess
不会使用shell,因为这样会比较慢,而且控制权会少一些。如果你真的需要用到管道或重定向,就必须明确告诉它使用shell。
通常情况下,我们会尽量避免使用管道(所以也就避免使用 shell=True
以及相关的问题),尽量在Python里完成这些操作(比如在你的例子中,可以使用Python标准库里的 gzip
模块)。当然,这样做的时候要小心,把标准输出(stdout,后面要处理的输出)和错误输出(stderr)分开,分别用两个管道处理。
给定路径、用户名、密码和数据库名后,下面的代码运行得非常顺利:
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))
我不太确定管道会怎么被解释。如果这有问题,你可以通过编程的方式来创建一个管道。
来源于: 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()