我想在Windows中使用subprocess.Popen()
rsync.exe,并在Python中打印stdout。
我的代码有效,但在文件传输完成之前它没有抓住进度!我想实时打印每个文件的进度。
现在使用Python 3.1,因为我听说它应该更好地处理IO。
import subprocess, time, os, sys
cmd = "rsync.exe -vaz -P source/ dest/"
p, line = True, 'start'
p = subprocess.Popen(cmd,
shell=True,
bufsize=64,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
for line in p.stdout:
print(">>> " + str(line.rstrip()))
p.stdout.flush()
答案 0 :(得分:86)
subprocess
的一些经验法则。
shell=True
。它不必要地调用一个额外的shell进程来调用你的程序。sys.argv
是一个列表,C中的argv
也是如此。所以你将列表传递给Popen
来调用子进程,而不是字符串。stderr
重定向到PIPE
。 stdin
。示例:
import subprocess, time, os, sys
cmd = ["rsync.exe", "-vaz", "-P", "source/" ,"dest/"]
p = subprocess.Popen(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
for line in iter(p.stdout.readline, b''):
print(">>> " + line.rstrip())
也就是说,rsync可能会在检测到它连接到管道而不是终端时缓冲其输出。这是默认行为 - 当连接到管道时,程序必须显式刷新stdout以获得实时结果,否则标准C库将缓冲。
要测试它,请尝试运行它:
cmd = [sys.executable, 'test_out.py']
并创建一个包含以下内容的test_out.py
文件:
import sys
import time
print ("Hello")
sys.stdout.flush()
time.sleep(10)
print ("World")
执行该子进程应该给你“Hello”并在给出“World”之前等待10秒。如果上面的python代码发生了这种情况而不是rsync
,那就意味着rsync
本身就是缓冲输出,所以你运气不好。
解决方案是使用pty
之类的内容直接连接到pexpect
。
答案 1 :(得分:31)
我知道这是一个古老的话题,但现在有一个解决方案。使用选项--outbuf = L调用rsync。例如:
cmd=['rsync', '-arzv','--backup','--outbuf=L','source/','dest']
p = subprocess.Popen(cmd,
stdout=subprocess.PIPE)
for line in iter(p.stdout.readline, b''):
print '>>> {}'.format(line.rstrip())
答案 2 :(得分:11)
在Linux上,我遇到了摆脱缓冲的同样问题。我终于用了#34; stdbuf -o0" (或者,从预期中解除缓冲)摆脱PIPE缓冲。
proc = Popen(['stdbuf', '-o0'] + cmd, stdout=PIPE, stderr=PIPE)
stdout = proc.stdout
然后我可以在stdout上使用select.select。
答案 3 :(得分:8)
for line in p.stdout:
...
始终会阻止,直到下一个换行。
对于“实时”行为,您必须执行以下操作:
while True:
inchar = p.stdout.read(1)
if inchar: #neither empty string nor None
print(str(inchar), end='') #or end=None to flush immediately
else:
print('') #flush for implicit line-buffering
break
当子进程关闭其stdout或退出时,会留下while循环。
read()/read(-1)
将阻止子进程关闭其stdout或退出。
答案 4 :(得分:7)
你的问题是:
for line in p.stdout:
print(">>> " + str(line.rstrip()))
p.stdout.flush()
迭代器本身有额外的缓冲。
尝试这样做:
while True:
line = p.stdout.readline()
if not line:
break
print line
答案 5 :(得分:5)
你不能把stdout打印到无管道打印(除非你可以重写打印到stdout的程序),所以这是我的解决方案:
将stdout重定向到没有缓冲的sterr。 '<cmd> 1>&2'
应该这样做。按如下方式打开流程:myproc = subprocess.Popen('<cmd> 1>&2', stderr=subprocess.PIPE)
你无法区分stdout或stderr,但你可以立即获得所有输出。
希望这可以帮助任何人解决这个问题。
答案 6 :(得分:2)
将rsync进程中的stdout更改为无缓冲。
p = subprocess.Popen(cmd,
shell=True,
bufsize=0, # 0=unbuffered, 1=line-buffered, else buffer-size
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
答案 7 :(得分:2)
为了避免缓存输出,你可能想尝试pexpect,
child = pexpect.spawn(launchcmd,args,timeout=None)
while True:
try:
child.expect('\n')
print(child.before)
except pexpect.EOF:
break
PS :我知道这个问题很老了,仍然提供适合我的解决方案。
PPS :从另一个问题得到了这个答案
答案 8 :(得分:2)
p = subprocess.Popen(command,
bufsize=0,
universal_newlines=True)
我正在python中编写rsync的GUI,并且有相同的问题。这个问题困扰了我好几天,直到我在pyDoc中找到它。
如果universal_newlines为True,则文件对象stdout和stderr将以通用换行模式打开为文本文件。行可以通过以下任何方式终止:Unix终止约定,&#39; \ r&#39;旧的Macintosh约定或&#39; \ r \ n&# 39;,Windows约定。所有这些外部表征都被视为&#39; \ n&#39;通过Python程序。
似乎rsync会输出&#39; \ r&#39;正在进行翻译。
答案 9 :(得分:1)
根据使用情况,您可能还希望在子流程本身中禁用缓冲。
如果子进程将是Python进程,则可以在调用之前执行此操作:
os.environ["PYTHONUNBUFFERED"] = "1"
或者将其在env
参数中传递给Popen
。
否则,如果您使用的是Linux / Unix,则可以使用stdbuf
工具。例如。喜欢:
cmd = ["stdbuf", "-oL"] + cmd
另请参见here关于stdbuf
或其他选项。
答案 10 :(得分:0)
我注意到没有提到使用临时文件作为中间文件。以下通过输出到临时文件来解决缓冲问题,并允许您解析来自rsync的数据而无需连接到pty。我在linux机器上测试了以下内容,并且rsync的输出在不同平台上有所不同,因此解析输出的正则表达式可能会有所不同:
import subprocess, time, tempfile, re
pipe_output, file_name = tempfile.TemporaryFile()
cmd = ["rsync", "-vaz", "-P", "/src/" ,"/dest"]
p = subprocess.Popen(cmd, stdout=pipe_output,
stderr=subprocess.STDOUT)
while p.poll() is None:
# p.poll() returns None while the program is still running
# sleep for 1 second
time.sleep(1)
last_line = open(file_name).readlines()
# it's possible that it hasn't output yet, so continue
if len(last_line) == 0: continue
last_line = last_line[-1]
# Matching to "[bytes downloaded] number% [speed] number:number:number"
match_it = re.match(".* ([0-9]*)%.* ([0-9]*:[0-9]*:[0-9]*).*", last_line)
if not match_it: continue
# in this case, the percentage is stored in match_it.group(1),
# time in match_it.group(2). We could do something with it here...
答案 11 :(得分:0)
如果您在线程中运行类似的操作并将ffmpeg_time属性保存在方法的属性中,以便您可以访问它,那么它将很好用 我得到这样的输出: output be like if you use threading in tkinter
input = 'path/input_file.mp4'
output = 'path/input_file.mp4'
command = "ffmpeg -y -v quiet -stats -i \"" + str(input) + "\" -metadata title=\"@alaa_sanatisharif\" -preset ultrafast -vcodec copy -r 50 -vsync 1 -async 1 \"" + output + "\""
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=True)
for line in self.process.stdout:
reg = re.search('\d\d:\d\d:\d\d', line)
ffmpeg_time = reg.group(0) if reg else ''
print(ffmpeg_time)
答案 12 :(得分:-1)
在Python 3中,这是一个解决方案,该解决方案从命令行中删除命令,并在接收到正确的实时字符串后提供实时解码的字符串。
接收者(receiver.py
):
import subprocess
import sys
cmd = sys.argv[1:]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
for line in p.stdout:
print("received: {}".format(line.rstrip().decode("utf-8")))
可以生成实时输出(dummy_out.py
)的示例简单程序:
import time
import sys
for i in range(5):
print("hello {}".format(i))
sys.stdout.flush()
time.sleep(1)
输出:
$python receiver.py python dummy_out.py
received: hello 0
received: hello 1
received: hello 2
received: hello 3
received: hello 4