首页 > 解决方案 > 子进程 Python 模块:不会编写 .csv,但如果单独运行命令会这样做

问题描述

我正在研究信号处理,当然也在使用 python,但是我有一些 matlab 代码可以进行一些重要的预处理。我认为我可以让我的部分管道通过 matlab 运行,而不是重写为 python。

我已经尝试通过subprocesspython 模块执行此操作,因为我知道我可以通过终端运行 matlab。

我的问题:

import subprocess

myBashCommands = [] #we'll populate this list with our desired bash commands to get the smooth PSD.
myBashCommands.append("cd /Applications/MATLAB_R2018b.app/bin") #go inside matlab package
myBashCommands.append("./matlab -nodesktop") #now we're in matlab via terminal
myBashCommands.append("potato_wav = audioread('potato-us.wav')") #create the .wav
myBashCommands.append("[f, t,  psd] = GetSpectrogram.m('potato_wav', 16000, 10)") #compute the spectrogram
myBashCommands.append("smooth_psd = smoothn(10*log(psd), .5)") #smooth it
myBashCommands.append("smooth_psd = SurfaceCubicInterpolator(smooth_psd)") #smooth the surface
myBashCommands.append("dlmwrite('smooth_psd.csv', smooth_psd, 'precision', ',', 'precision', '%.10f')") #overwrite or create a new .csv in the folder

subprocess.run(myBashCommands,stderr=PIPE, stdout=PIPE, shell = True, check = True) #run all of these 

除了成功完成的流程消息外,什么都不返回。如果我使用subprocess.call,我会得到一个 0 返回,这也表示成功。

同样,当我在新的终端窗口中单独运行所有这些 bash 命令时,我成功获得了 .csv。

关于为什么在使用子进程动态运行时不会创建 .csv 的任何想法?我需要它来接收任意 .wav 文件,所以这似乎是在不创建某种 bash 脚本的情况下执行此操作的最简单的方法。

标签: pythonbashmatlabsubprocess

解决方案


subprocess是一个棘手的工具。首先要注意的是,每次调用subprocess.run()都会调用一个新进程。其他所有内容都将被视为该新进程的参数(argv在 C 中)。运行您发布的代码将转换为:

cd /Applications/MATLAB_R2018b.app/bin [lots of other stuff...]

由于cd将使用第一个参数并忽略任何其他参数,因此它成功完成并返回。

这是一个更清楚地证明它的测试:

>>> cmds=['cd .. ls who what where when why']
>>> sp.run(cmds, shell=True)
CompletedProcess(args=['cd .. ls who what where when why'], returncode=0)

如果我们;pwd在 末尾添加 a cmds[0],它将显示比任何os.getcwd()显示都高一级。但随后的调用os.getcwd()将显示与最初相同的位置。

简单的解决方案是将所有命令分解为它们自己的subprocess.run()调用。但这也不会完全正确。原因是诸如此类的cd事情不会应用于您当前的会话,因此以您希望的方式更改工作目录是行不通的。

要使用多个subprocess.run调用来完成此操作,需要组合模块subprocess中的一些功能os。具体来说,os.chdir()。但即便如此,您也可能会遇到与其他命令及其参数类似的问题。

快速而肮脏的方法是将所有命令放在一起,用分号分隔,但作为一个字符串(就像您在命令提示符下所做的那样)。然后,使用 to 的shell=True参数执行它run()(你已经在做)。基本上,这是:

myBashCommands = ["cd /Applications/MATLAB_R2018b.app/bin;"]
myBashCommands[0] += "./matlab -nodesktop;"
[....]
subprocess.run(myBashCommands,stderr=PIPE, stdout=PIPE, shell = True, check = True) 

正如评论中提到的,这里有一层额外的复杂性,因为发布的 Python 代码实际上包含嵌入式 Matlab 代码。有两种简单的方法可以处理:

  1. 将 Matlab 代码放在一个单独的文件中,并在调用matlab. 像这样的东西可能会起作用:
myBashCommands = ["cd /Applications/MATLAB_R2018b.app/bin;"]
myBashCommands[0] += "./matlab -nodesktop run('/path/to/script/file.m');"
subprocess.run(myBashCommands,stderr=PIPE, stdout=PIPE, shell = True, check = True) 
  1. 将嵌入的 Matlab 代码保留在 python 脚本中(这对我来说似乎有点笨拙),确保整个块被正确引用和转义,并将其作为命令行参数提供给 matlab:
myBashCommands = ["cd /Applications/MATLAB_R2018b.app/bin;"]
myBashCommands[0] += "./matlab -nodesktop 'potato_wav = audioread(\'potato-us.wav\'); [f, t,  psd] = GetSpectrogram.m(\'potato_wav\', 16000, 10); smooth_psd = smoothn(10*log(psd), .5); smooth_psd = SurfaceCubicInterpolator(smooth_psd); dlmwrite(\'smooth_psd.csv\', smooth_psd, \'precision\', \',\', \'precision\', \'%.10f\')"
subprocess.run(myBashCommands,stderr=PIPE, stdout=PIPE, shell = True, check = True) 

推荐阅读