Python - 在Windows中管道子进程

3 投票
3 回答
7081 浏览
提问于 2025-04-16 19:30

我在用Windows 7,尝试过Python 2.6.6和Python 3.2。

我想从Python里调用这个命令行:

netstat -ano | find ":80"

在Windows的命令提示符下,这行命令运行得很好。

所以,

  • 第一次尝试:

    output = subprocess.Popen(
               [r'netstat -ano | find ":80"'],
               stdout=subprocess.PIPE,
               shell=True
    ).communicate()
    

    出现了一个错误,提示'find'没有收到正确的参数(比如'find ":80" \'):

    Access denied - \
    
  • 第二次尝试:

    #calling netstat
    cmd_netstat = subprocess.Popen(
                    ['netstat','-ano'],
                    stdout = subprocess.PIPE
    )
    
    #pipelining netstat result into find
    cmd_find = subprocess.Popen(
                 ['find','":80"'],
                 stdin = cmd_netstat.stdout,
                 stdout = subprocess.PIPE
    )
    

    同样的错误又出现了。

    Access denied - \
    

我到底做错了什么呢? :(

编辑:

  • 第三次尝试(根据@Pavel Repin的建议):

    cmd_netstat = subprocess.Popen(
                    ['cmd.exe', '-c', 'netstat -ano | find ":80"'],
                    stdout=subprocess.PIPE
    ).communicate()
    

    不幸的是,用['cmd.exe','-c']的子进程结果像是死锁或者出现了一个空的命令窗口。我猜'-c'被命令提示符忽略了,导致communicate()一直在等命令提示符结束。因为这是Windows,我认为命令提示符只接受以斜杠(/)开头的参数。所以我把'-c'换成了'/c':

    cmd_netstat = subprocess.Popen(
                    ['cmd.exe', '/c', 'netstat -ano | find ":80"'],
                    stdout=subprocess.PIPE
    ).communicate()
    

    结果……又回到了同样的错误:

    Access denied - \
    

编辑: 我放弃了,我决定在Python里处理'netstat -ano'返回的字符串。这可能是个bug吗?

3 个回答

0

新的回答,经过再次阅读这个旧问题后,我觉得可能是由于以下两个原因:

  • 管道操作符会在一个子进程中执行后面的命令;你可以看看这个有趣的结果

  • Python本身使用管道来获取结果

    注意,要想在结果元组中得到除了None以外的任何东西,你需要同时设置stdout=PIPE和/或stderr=PIPE。

不过,我不确定这种“冲突”是一个bug,还是设计上的选择。

3

我建议你在Python代码中尽量多做事情。你可以执行以下命令:

# executing the command
import subprocess
output = subprocess.Popen(['netstat', '-ano'], stdout=subprocess.PIPE).communicate()

然后通过解析输出结果:

# filtering the output
valid_lines = [ line for line in output[0].split('\r\n') if ':80' in line ]

你会得到一系列的行。在我的电脑上,端口号1900的输出看起来是这样的(没有激活html连接):

['  UDP    127.0.0.1:1900         *:*                                    1388', '  UDP    192.xxx.xxx.233:1900    *:*                                    1388']

我觉得这样处理起来更简单。

注意:

  • 选项shell=True不是必须的,但会快速打开和关闭一个命令行窗口。看看哪个更适合你,但要注意命令注入的问题;
  • Popen的参数列表应该是一个字符串列表。列表中的部分不需要加引号,subprocess会帮你处理这些。

希望这对你有帮助。

编辑:哎呀,我漏掉了最后一行的编辑。看起来你已经自己想明白了。

3

我重新看了一下这个问题,发现了两个解决方案(我之前换到了Python 2.7,所以对Python 2.6不太确定,但应该是一样的):

  1. find换成findstr,并去掉双引号

    output = subprocess.Popen(['netstat','-ano','|','findstr',':80'],
                              stdout=subprocess.PIPE,
                              shell=True)
                       .communicate()
    

    不过这并没有解释为什么不能用"find",所以:

  2. 用字符串参数代替列表

    output = subprocess.Popen('netstat -ano | find ":80"',
                              stdout=subprocess.PIPE,
                              shell=True)
                       .communicate()
    

    或者

    pipeout = subprocess.Popen(['netstat', '-ano'], 
                               stdout = subprocess.PIPE)
    output = subprocess.Popen('find ":80"', 
                              stdin = pipeout.stdout, 
                              stdout = subprocess.PIPE)
                       .communicate()
    

问题出在:['find','":80"']实际上被转换成了['find,'\":80\"']。

因此在Windows命令行中执行的命令是:

>find \":80\"
Access denied - \

证明:

  • 运行:

    output = subprocess.Popen(['echo','find','":80"'],
                              stdout=subprocess.PIPE,
                              shell=True)
                       .communicate()
    print output[0]
    

    返回:

    find \":80\"
    
  • 运行:

    output = subprocess.Popen('echo find ":80"',
                              stdout=subprocess.PIPE,
                              shell=True)
                       .communicate()
    print output[0]
    

    返回:

    find ":80"
    

撰写回答