如何使python脚本在bash和python中都可以管道化

如何使python脚本在bash和python中都可以管道化,python,pipeline,python-multithreading,Python,Pipeline,Python Multithreading,摘要:我想在命令行上编写类似bash脚本的python脚本,但是我还想在python中轻松地将它们连接在一起。我遇到的麻烦是胶水使后者发生 想象一下,我写了两个脚本,script1.py和script2.py,我可以像这样将它们连接在一起: echo input_string | ./script1.py -a -b | ./script2.py -c -d 如何从另一个python文件中获取此行为? 这是我知道的方式,但我不喜欢: arg_string_1 = convert_to_args

摘要:我想在命令行上编写类似bash脚本的python脚本,但是我还想在python中轻松地将它们连接在一起。我遇到的麻烦是胶水使后者发生

想象一下,我写了两个脚本,
script1.py
script2.py
,我可以像这样将它们连接在一起:

echo input_string | ./script1.py -a -b | ./script2.py -c -d
如何从另一个python文件中获取此行为? 这是我知道的方式,但我不喜欢:

arg_string_1 = convert_to_args(param_1, param_2)
arg_string_2 = convert_to_args(param_3, param_4)
output_string = subprocess.check_output("echo " + input_string + " | ./script1.py " + arg_string_1 + " | ./script2.py " + arg_string_2)
如果我不想利用多线程,我可以这样做(?):


这是我尝试的方法,但我在写胶水时遇到了麻烦。我很感激学习如何完成下面的方法,或者为更好的设计/方法提出建议

我的方法:我编写了
script1.py
script2.py
如下:

#!/usr/bin/python3

... # import sys and define "parse_args"

def main(param_1, param_2, input, output):
   for line in input:
     ...
     print(stuff, file=output)

if __name__ == "__main__":
  parameter_1, parameter_2 = parse_args(sys.argv)
  main(parameter_1, parameter_2, sys.stdin, sys.stdout)
然后我想写这样的东西,但不知道如何完成:

pipe_out, pipe_in = ????
output = StringIO()
thread_1 = Thread(target=script1.main, args=(param_1, param_2, StreamIO(input_string), pipe_out))
thread_2 = Thread(target=script2.main, args=(param_3, param_4, pipe_in, output)
thread_1.start()
thread_2.start()
thread_1.join()
thread_2.join()
output_str = output.get_value()
对于“管道输入”,使用
sys.stdin
readlines()
方法。(使用方法
read()
将一次读取一个字符。)

要将信息从一个线程传递到另一个线程,可以使用
Queue
。您必须定义一种发送数据结束信号的方法。在我的示例中,由于线程之间传递的所有数据都是
str
,因此我只需使用
None
对象来表示数据的结束(因为它不能出现在传输的数据中)

还可以使用更多线程,或者在线程中使用不同的函数

为了保持简单,我没有在示例中包含
sys.argv
。修改它以获取参数(
parameter1
,…)应该很容易

import sys
from threading import Thread
from Queue import Queue
import fileinput

def stdin_to_queue( output_queue ):
  for inp_line in sys.stdin.readlines():     # input one line at at time                                                
    output_queue.put( inp_line, True, None )  # blocking, no timeout
  output_queue.put( None, True, None )    # signal the end of data                                                  


def main1(input_queue, output_queue, arg1, arg2):
  do_loop = True
  while do_loop:
    inp_data = input_queue.get(True)
    if inp_data is None:
      do_loop = False
      output_queue.put( None, True, None )  # signal end of data                                                    
    else:
      out_data = arg1 + inp_data.strip('\r\n').upper() + arg2 #  or whatever transformation...                                    
      output_queue.put( out_data, True, None )

def queue_to_stdout(input_queue):
  do_loop = True
  while do_loop:
    inp_data = input_queue.get(True)
    if inp_data is None:
      do_loop = False
    else:
      sys.stdout.write( inp_data )


def main():
  q12 = Queue()
  q23 = Queue()
  q34 = Queue()
  t1 = Thread(target=stdin_to_queue, args=(q12,) )
  t2 = Thread(target=main1, args=(q12,q23,'(',')') )
  t3 = Thread(target=main1, args=(q23,q34,'[',']') )
  t4 = Thread(target=queue_to_stdout, args=(q34,))
  t1.start()
  t2.start()
  t3.start()
  t4.start()


main()
最后,我用一个文本文件测试了这个程序(python2)

head sometextfile.txt | python script.py 

根据脚本是否从命令行运行,将返回值重定向到stdout:

#!/usr/bin/python3
import sys

# Example function
def main(input):
    # Do something with input producing stuff
    ...
    return multipipe(stuff)

if __name__ == '__main__':
    def multipipe(data):
        print(data)

    input = parse_args(sys.argv)
    main(input)
else:
    def multipipe(data):
        return data
每个脚本都有相同的两个定义
multipipe
。现在,使用
multipipe
进行输出

如果从命令行
$./scrip1.py |/scrip2.py
一起调用所有脚本,则每个脚本都将具有
\uuuu name\uuu=='\uuuu main\uuuu'
,因此
多管道
将全部打印到stdout,以供下一个脚本作为参数读取(并返回
None
,因此每个函数都返回
None
,但在本例中,您不会查看返回值)

如果在其他python脚本中调用它们,则每个函数将返回传递给
multipipe
的任何内容

实际上,您可以使用现有的函数,只需将
print(stuff,file=output)
替换为
returnmultipipe(stuff)
。非常简单


要将其用于多线程或多处理,请设置函数,使每个函数都返回一件事,然后将它们插入到一个简单的函数中,将数据添加到多线程队列中。有关此类排队系统的示例,请参阅。使用该示例,只需确保管道中的每个步骤都将
None
(或者您选择的其他哨兵值-我喜欢
,因为您很少会因为任何原因将
省略号
对象传递到队列中的下一个对象以表示完成。

使用标准
Popen
类有一个非常简单的解决方案

下面是一个例子:

#this is the master python program
import subprocess
import sys
import os

#note the use of stdin and stdout arguments here
process1 = subprocess.Popen(['./script1.py'], stdin=sys.stdin, stdout=subprocess.PIPE)
process2 = subprocess.Popen(['./script2.py'], stdin=process1.stdout)

process1.wait()
process2.wait()
这两个脚本是:

#!/usr/bin/env python
#script1.py
import sys

for line in sys.stdin:
    print(line.strip().upper())
这是第二个

#!/usr/bin/env python
#script2.py
import sys

for line in sys.stdin:
    print("<{}>".format(line.strip()))
!/usr/bin/env python
#脚本2.py
导入系统
对于sys.stdin中的行:
打印(“.”格式(line.strip())

谢谢,这看起来不错,只是代码太多了。我本来希望有更简洁的东西。但是如果性能好的话,这可能是值得的。有很多方法可以减少它。例如,你可以直接从一个文件读取,然后在同一个线程中直接写入一个文件。我特意将很多东西分离到illustr中单独吃了很多东西。谢谢,这肯定比我的解决方案好。它仍然保留处理参数的方法。此解决方案似乎需要获取参数对象,将其转换为字符串,将其传递给Popen,然后process1解析字符串并重新创建对象。直接传递就好了对象通过。@usul参数没有问题,只需将
['./script1.py',param1',param2'.
放在对Popen的调用中,而不只是
['./script1.py']
Hi@Yoav,是的,如果参数已经是字符串,这很好。但是如果参数是列表或更复杂的对象,就没有那么好了。例如,如果我有一个datetime对象,我必须首先将其转换为字符串,然后传入,然后script1.py必须再次将其解析为datetime对象。@usul,嗯-命令行参数总是strings—这是无法逃避的。如果您想在Python进程之间通信Python对象,有比在命令行上以字符串形式传递它们更好的解决方案。但是,从最初的问题中,我发现您想知道如何进行管道处理—这正是我要解决的问题。如果您知道您正在运行Python进程,例如,你可以在它们之间通过管道传输pickle对象。是的,问题是我也在试着吃我的蛋糕。我想支持从命令行调用脚本(参数是字符串)和从python调用脚本(参数是进程),在这两种情况下我都想通过管道传输信息。
#!/usr/bin/env python
#script2.py
import sys

for line in sys.stdin:
    print("<{}>".format(line.strip()))