将 "shell script" 作为字符串传递给 Python subprocess.Popen
Passing a "shell script" as a string to Python subprocess.Popen
我想像 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...
...
这是为什么?我是否遗漏了导致不同(意外)输出的格式?
假设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 (#!
) 被视为 mete 注释:
$ sh -c '#! /usr/local/bin/python
a = 10
for i in range(10): print i
'
a: not found
Syntax error: "(" unexpected
我想你想要的是将文件名正确传递给脚本,这样就可以了吗?
p = src.popen( ['sh', './timer.sh', 'filename.txt'], stdout=subprocess.PIPE )
并在脚本中执行
FILENAME=""
....
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)
最好避免完全使用 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)
要将 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,))
我想像 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...
...
这是为什么?我是否遗漏了导致不同(意外)输出的格式?
假设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 (#!
) 被视为 mete 注释:
$ sh -c '#! /usr/local/bin/python
a = 10
for i in range(10): print i
'
a: not found
Syntax error: "(" unexpected
我想你想要的是将文件名正确传递给脚本,这样就可以了吗?
p = src.popen( ['sh', './timer.sh', 'filename.txt'], stdout=subprocess.PIPE )
并在脚本中执行
FILENAME=""
....
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)
最好避免完全使用 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)
要将 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,))