【问题标题】:Passing a "shell script" as a string to Python subprocess.Popen将“shell脚本”作为字符串传递给Python subprocess.Popen
【发布时间】:2015-07-30 11:49:55
【问题描述】:

我想执行一个字符串,就像它是 Mininet 的 host.popen() 模块中的 shell 脚本一样,它本质上是 Python 的 subprocess.Popen() 的包装器。脚本如下:

#!/bin/bash

T="$(date +%s%N)" 
nc 10.0.0.7 1234 < somefile.txt
T="$(($(date +%s%N)-T))" 
echo $T

如果我将它保存为文件并将其传递给popen(),则输出如预期(打印 nc 命令的持续时间):

dst_cmd = 'nc -l 1234 > /dev/null'
dst.popen( dst_cmd, shell=True )

p = src.popen( ['sh', './timer.sh'], stdout=subprocess.PIPE )

问题是每次我运行此脚本时,发送的文件somefile.txt 都会有所不同,并且它每秒运行几次,持续几分钟。我不想为每个文件编写一个新的 .sh 脚本。当我尝试将脚本作为字符串传递到popen() 时,像这样,

dst_cmd = 'nc -l 1234 > /dev/null'
dst.popen( dst_cmd, shell=True )

src_cmd = '''\
    #!/bin/bash

    T=\"$(date +%s%N)\" 
    nc 10.0.0.7 1234 < somefile.txt
    T=\"$(($(date +%s%N)-T))\" 
    echo $T'''

p = src.popen( dedent(src_cmd), shell=True, 
                stdout=subprocess.PIPE )    

输出是

Execution utility for Mininet

Usage: mnexec [-cdnp] [-a pid] [-g group] [-r rtprio] cmd args...
...

这是为什么呢?我是否遗漏了导致不同(意外)输出的格式?

【问题讨论】:

  • 什么叫mnexecnc 连接到什么?
  • nc 正在连接到网络中的另一台主机(代码已更新以显示这一点)。我不确定mnexec 在这里做什么;它作为参数传递给Popen() 调用:'mncmd' = [ 'mnexec', '-da', str( self.pid ) ] 我从Mininet 源代码here 得到这个。

标签: python bash shell subprocess mininet


【解决方案1】:

我认为你想要的是将文件名传递给脚本,这样就可以了?

p = src.popen( ['sh', './timer.sh', 'filename.txt'], stdout=subprocess.PIPE )

在脚本中做

FILENAME="$1"
....
nc 10.0.0.7 1234 < "$FILENAME"

这应该可以解决问题。或者我可能完全误解了这个问题。

======

编辑:

作为替代方案(以及更接近实际问题的答案),您可以这样做:

cmd = """echo $(date) HELLO $(date)"""
p = subprocess.Popen(["sh", "-c", cmd])

sh -c 告诉 shell 以文字形式执行下一个命令。请注意,您不需要shell=True,因为您不需要对代码进行任何解析

编辑 2:

这就是你回答太快的结果。您确实可以通过这种方式将完整的 shell 脚本传递给 shell。只是不要进行任何转义(并可能摆脱 shebang):

cmd = """echo $(date) HELLO $(date);
   sleep 1;
   echo $(date)
"""
subprocess.Popen(cmd, shell=True)

【讨论】:

  • 哇。这应该是显而易见的。这很有效,而且比传入字符串要优雅得多!但我仍然不明白为什么其他方式不起作用。根据@Serge Ballesta 的评论,我将脚本逐行粘贴到 shell 提示符中,它必须是一个 shell 命令,虽然这是一个糟糕的时序测试,但它仍然返回一个合理的输出(一串数字)。我还尝试了一条以分号分隔的行,但无济于事。我对此感到好奇,因为如果多个子进程尝试同时读取此文件,考虑到我的设置,这可能会导致问题,对吗?
  • 不知道为什么它不起作用。实际上,它适用于我的设置。另外,考虑到您的错误,这实际上似乎不是问题。从多个进程读取同一个文件确实不是问题
【解决方案2】:

最好避免完全使用 shell。您可以通过以下方式更简单地做您想做的事:

from subprocess import Popen, DEVNULL
from time import time

start = time()
with open("somefile.txt", "rb") as f:
    p = Popen(["nc", "10.0.0.7", "1234"], stdin=f, stdout=DEVNULL)
end = time()

duration = end - start

如果您担心子进程的生成时间很长,请尝试:

from subprocess import Popen, DEVNULL, PIPE
from time import time
from os import devnull


with open("somefile.txt", "rb") as f:
    data = f.read()

p = Popen(["nc", "10.0.0.7", "1234"], stdin=PIPE, stdout=DEVNULL)
start = time()
p.communicate(data)
end = time()

duration = end - start

print(duration)

【讨论】:

  • 这不包括产生子进程的开销吗?我正在定时传输,这些传输通常非常短,大​​约为毫秒。
  • 您可以使用Popen.communicate 来延迟数据的发送,直到进程产生。老实说,我认为等待硬盘读取somefile.txt 的开销将明显高于子进程的生成。
  • @Toni:你为什么要使用nc 命令? stdlib 中有socket 模块。
  • @J.F.Sebastian,文件正在 shell 进程之间发送(我认为这是正确的术语)。源和目标是 Mininet 网络中的“主机”。我可能可以在每个脚本上运行一个 Python 脚本,但使用 nc 似乎更简单、更快捷。
【解决方案3】:

要将 bash 脚本作为字符串传递,请指定 executable 参数:

#!/usr/bin/env python
import subprocess

bash_string = r'''#!/bin/bash
T="$(date +%s%N)" 
nc 10.0.0.7 1234 < somefile.txt
T="$(($(date +%s%N)-T))" 
echo $T
'''
output = subprocess.check_output(bash_string, shell=True, executable='/bin/bash')

虽然在这种情况下您既不需要 shell,也不需要任何其他外部进程。您可以使用 socket 模块在纯 Python 中重新实现 shell 脚本:

#!/usr/bin/env python3
import socket
import sys
from shutil import copyfileobj
from timeit import default_timer as timer

start = timer()
with socket.create_connection(('10.0.0.7', 1234)) as s, \
     open('somefile.txt', 'rb') as input_file:
    s.sendfile(input_file) # send file
    with s.makefile() as f: # read response
        copyfileobj(f, sys.stdout)
print("It took %.2f seconds" % (timer() - start,))

【讨论】:

  • 不完全是。至少 shebang #! 被忽略了。您的代码将起作用,因为您通过bash 强制执行。但在 shell (cmd.exe) 不直接支持多行命令的 Windows 系统上会完全不同。
  • @SergeBallesta:1. 显然,shebang 被忽略了。否则,您不需要显式传递executable。 2. Windows 是一个完全不同的问题
【解决方案4】:

假设mininet popen接口和Subprocess.popen一样,那你写的不对。你必须传递一个命令,可以是:

  • 可执行文件及其参数
  • 或 shell 命令,前提是您使用 shell=True - 这是您可以在 shell 提示符下键入的内容

但不能是shell脚本的内容

当然,如果脚本可以被视为多行命令,并且您的 shell 支持多行命令,则将执行单个命令。因此,如果您的默认 shell 已经是 bash,它可以工作,但如果默认 shell 是 /bin/ash,例如,所有命令都将由该默认 shell 执行,因为行 #!/bin/sh 将被视为仅仅是注释和忽略。

例子:

foo.py:

#! /usr/local/bin/python

a = 5
for i in range(10): print i

文件执行正确

$ sh -c ./foo.py
0
1
2
3
4

但文件内容的执行会导致错误,因为 shebang (#!) 被视为一条评论:

$ sh -c '#! /usr/local/bin/python

a = 10
for i in range(10): print i
'
a: not found
Syntax error: "(" unexpected

【讨论】:

  • 你可以pass the content of a shell script if you wantsubprocess 运行 [executable, '-c', shell_script_as_string] 如果你通过了 shell=True -- 如果 executable/bin/sh, /bin/bash 支持多行命令(他们支持),它会工作。
  • @J.F.Sebastian:不完全是。它应该在这里工作,假设默认 shell 已经是 bash,但它不能在我的默认 shell 不是 bash 的 FreeBSD 盒子中工作。
  • subprocess 默认使用/bin/sh。你是说它不支持 FreeBSD 上的多行命令?
  • @J.F.Sebastian : 不,FreeBSD 是一个不错的操作系统 :-),我只是说命令不会通过bash 执行。脚本中是否有任何特定于 bash 的扩展名可能很重要 - 因为它以 #! /bin/bash 开头,所以它可能会用 bash 执行
  • 这就是为什么我在你已经看到的答案中明确传递/bin/bash
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-01-08
  • 2015-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-21
  • 2022-12-09
相关资源
最近更新 更多