Domanda

Come posso eseguire il seguente comando shell usando Python sottoprocesso modulo?

echo "input data" | awk -f script.awk | sort > outfile.txt

I dati di input provengono da una stringa, quindi in realtà non ho bisogno di echo . Sono arrivato così lontano, qualcuno può spiegare come riesco a passare anche attraverso sort ?

p_awk = subprocess.Popen(["awk","-f","script.awk"],
                          stdin=subprocess.PIPE,
                          stdout=file("outfile.txt", "w"))
p_awk.communicate( "input data" )

AGGIORNAMENTO : nota che, sebbene la risposta accettata di seguito non risponda effettivamente alla domanda come posta, credo che S.Lott abbia ragione ed è meglio evitare di dover risolvere il problema in primo luogo !

È stato utile?

Soluzione

Saresti un po 'più felice di quanto segue.

import subprocess

awk_sort = subprocess.Popen( "awk -f script.awk | sort > outfile.txt",
    stdin=subprocess.PIPE, shell=True )
awk_sort.communicate( b"input data\n" )

Delega parte del lavoro alla shell. Lascia che connetta due processi con una pipeline.

Saresti molto più felice di riscrivere 'script.awk' in Python, eliminando awk e la pipeline.

Modifica . Alcuni dei motivi per suggerire che awk non aiuta.

[Ci sono troppi motivi per rispondere tramite commenti.]

  1. Awk sta aggiungendo un passaggio senza valore significativo. Non c'è niente di unico nell'elaborazione di awk che Python non gestisce.

  2. Il pipelining da awk a sort, per grandi serie di dati, può migliorare il tempo di elaborazione trascorso. Per brevi set di dati, non ha vantaggi significativi. Una rapida misurazione del file awk >; ordina i file e awk | sort rivelerà gli aiuti della concorrenza. Con l'ordinamento, aiuta raramente perché l'ordinamento non è un filtro una tantum.

  3. La semplicità di " Python per ordinare " l'elaborazione (invece di " Python in awk per ordinare ") impedisce il tipo esatto di domande poste qui.

  4. Python - sebbene più intenso di awk - è anche esplicito in cui awk ha alcune regole implicite che sono opache per i neofiti e confuse per i non specialisti.

  5. Awk (come lo stesso script di shell) aggiunge l'ennesimo linguaggio di programmazione. Se tutto ciò può essere fatto in una sola lingua (Python), l'eliminazione della shell e la programmazione awk eliminano due linguaggi di programmazione, consentendo a qualcuno di concentrarsi sulle parti che generano valore dell'attività.

In conclusione: awk non può aggiungere un valore significativo. In questo caso, awk è un costo netto; ha aggiunto abbastanza complessità da rendere necessaria questa domanda. Rimuovere awk sarà un guadagno netto.

Barra laterale Perché costruire una pipeline ( a | b ) è così difficile.

Quando la shell si confronta con a | b deve eseguire le seguenti operazioni.

  1. Fork un processo figlio della shell originale. Questo alla fine diventerà b.

  2. Crea una pipe os. (non un sottoprocesso di Python.PIPE) ma chiama os.pipe () che restituisce due nuovi descrittori di file collegati tramite un buffer comune. A questo punto il processo ha stdin, stdout, stderr dal suo genitore, oltre a un file che sarà "stdout di" a " e "stdin di b".

  3. Fork un bambino. Il bambino sostituisce il suo stdout con il nuovo stdout della a. Eseguire il processo a .

  4. Il bambino b chiude sostituisce il suo stdin con il nuovo b dello stdin. Eseguire il processo b .

  5. Il bambino b attende il completamento di a.

  6. Il genitore sta aspettando il completamento di b.

Penso che quanto sopra possa essere usato ricorsivamente per generare a | b | c , ma devi racchiudere implicitamente tra parentesi lunghe, trattandole come se fossero a | (b | c) .

Poiché Python ha os.pipe () , os.exec () e os.fork () e puoi sostituire < code> sys.stdin e sys.stdout , c'è un modo per fare quanto sopra in puro Python. In effetti, potresti essere in grado di elaborare alcune scorciatoie usando os.pipe () e subprocess.Popen .

Tuttavia, è più semplice delegare tale operazione alla shell.

Altri suggerimenti

import subprocess

some_string = b'input_data'

sort_out = open('outfile.txt', 'wb', 0)
sort_in = subprocess.Popen('sort', stdin=subprocess.PIPE, stdout=sort_out).stdin
subprocess.Popen(['awk', '-f', 'script.awk'], stdout=sort_in, 
                 stdin=subprocess.PIPE).communicate(some_string)

Per emulare una pipeline di shell:

from subprocess import check_call

check_call('echo "input data" | a | b > outfile.txt', shell=True)

senza invocare la shell (consultare 17.1.4.2. Sostituzione della shell conduttura ):

#!/usr/bin/env python
from subprocess import Popen, PIPE

a = Popen(["a"], stdin=PIPE, stdout=PIPE)
with a.stdin:
    with a.stdout, open("outfile.txt", "wb") as outfile:
        b = Popen(["b"], stdin=a.stdout, stdout=outfile)
    a.stdin.write(b"input data")
statuses = [a.wait(), b.wait()] # both a.stdin/stdout are closed already

plumbum fornisce un po 'di zucchero di sintassi:

#!/usr/bin/env python
from plumbum.cmd import a, b # magic

(a << "input data" | b > "outfile.txt")()

L'analogo di:

#!/bin/sh
echo "input data" | awk -f script.awk | sort > outfile.txt

è:

#!/usr/bin/env python
from plumbum.cmd import awk, sort

(awk["-f", "script.awk"] << "input data" | sort > "outfile.txt")()

http://www.python.org/doc/2.5.2/lib/node535.html ha trattato abbastanza bene. C'è una parte di questo che non hai capito?

Il tuo programma sarebbe abbastanza simile, ma il secondo Popen avrebbe stdout = in un file e non avresti bisogno dell'output del suo .communicate () .

Ispirato dalla risposta di @ Cristian. Ho incontrato lo stesso problema, ma con un comando diverso. Quindi sto dando il mio esempio testato, che credo possa essere utile:

grep_proc = subprocess.Popen(["grep", "rabbitmq"],
                             stdin=subprocess.PIPE, 
                             stdout=subprocess.PIPE)
subprocess.Popen(["ps", "aux"], stdout=grep_proc.stdin)
out, err = grep_proc.communicate()

Questo è testato.

Cosa è stato fatto

  • Esecuzione dichiarata lazy grep con stdin dalla pipe. Questo comando verrà eseguito durante l'esecuzione del comando ps quando la pipe verrà riempita con lo stdout di ps .
  • Chiamato il comando principale ps con stdout diretto alla pipe utilizzata dal comando grep .
  • Grep ha comunicato per ottenere stdout dalla pipe.

Mi piace in questo modo perché è una concezione naturale delle pipe delicatamente avvolta dalle interfacce subprocess .

La risposta accettata sta evitando il problema. ecco uno snippet che concatena l'output di più processi: Si noti che stampa anche il comando (in qualche modo equivalente) della shell in modo da poterlo eseguire e assicurarsi che l'output sia corretto.

#!/usr/bin/env python3

from subprocess import Popen, PIPE

# cmd1 : dd if=/dev/zero bs=1m count=100
# cmd2 : gzip
# cmd3 : wc -c
cmd1 = ['dd', 'if=/dev/zero', 'bs=1M', 'count=100']
cmd2 = ['tee']
cmd3 = ['wc', '-c']
print(f"Shell style : {' '.join(cmd1)} | {' '.join(cmd2)} | {' '.join(cmd3)}")

p1 = Popen(cmd1, stdout=PIPE, stderr=PIPE) # stderr=PIPE optional, dd is chatty
p2 = Popen(cmd2, stdin=p1.stdout, stdout=PIPE)
p3 = Popen(cmd3, stdin=p2.stdout, stdout=PIPE)

print("Output from last process : " + (p3.communicate()[0]).decode())

# thoretically p1 and p2 may still be running, this ensures we are collecting their return codes
p1.wait()
p2.wait()
print("p1 return: ", p1.returncode)
print("p2 return: ", p2.returncode)
print("p3 return: ", p3.returncode)

EDIT: pipe è disponibile su Windows ma, soprattutto, non sembra effettivamente funzionare su Windows. Vedi i commenti qui sotto.

La libreria standard Python ora include il modulo pipe per la gestione di questo:

https://docs.python.org/2/library/pipes.html , https://docs.python.org/3.4/library/pipes.html

Non sono sicuro da quanto tempo questo modulo è in circolazione, ma questo approccio sembra essere molto più semplice di quello che si fa con sottoprocesso .

Le risposte precedenti hanno mancato un punto importante. La sostituzione della pipeline della shell è sostanzialmente corretta, come sottolineato di geocar. È quasi sufficiente eseguire communic sull'ultimo elemento della pipe.

Il problema rimanente sta passando i dati di input alla pipeline. Con più sottoprocessi, un semplice communic (input_data) sull'ultimo elemento non funziona - si blocca per sempre. Devi creare una pipeline e un figlio manualmente in questo modo:

import os
import subprocess

input = """\
input data
more input
""" * 10

rd, wr = os.pipe()
if os.fork() != 0: # parent
    os.close(wr)
else:              # child
    os.close(rd)
    os.write(wr, input)
    os.close(wr)
    exit()

p_awk = subprocess.Popen(["awk", "{ print $2; }"],
                         stdin=rd,
                         stdout=subprocess.PIPE)
p_sort = subprocess.Popen(["sort"], 
                          stdin=p_awk.stdout,
                          stdout=subprocess.PIPE)
p_awk.stdout.close()
out, err = p_sort.communicate()
print (out.rstrip())

Ora il figlio fornisce l'input attraverso la pipe e le chiamate principali comunicano (), che funziona come previsto. Con questo approccio, puoi creare condutture lunghe arbitrarie senza ricorrere a "delegare parte del lavoro alla shell". Sfortunatamente la documentazione sui sottoprocessi non menziona questo.

Esistono modi per ottenere lo stesso effetto senza pipe:

from tempfile import TemporaryFile
tf = TemporaryFile()
tf.write(input)
tf.seek(0, 0)

Ora usa stdin = tf per p_awk . È una questione di gusti ciò che preferisci.

Quanto sopra non è ancora equivalente al 100% alle pipeline di bash perché la gestione del segnale è diversa. Puoi vederlo se aggiungi un altro elemento pipe che tronca l'output di sort , ad es. head -n 10 . Con il codice sopra, sort stamperà un " Broken pipe " messaggio di errore a stderr . Non vedrai questo messaggio quando esegui la stessa pipeline nella shell. (Questa è l'unica differenza, il risultato in stdout è lo stesso). Il motivo sembra essere che Popen di Python imposta SIG_IGN per SIGPIPE , mentre la shell lo lascia su SIG_DFL , e La gestione del segnale di sort è diversa in questi due casi.

Per me, l'approccio di seguito è il più pulito e facile da leggere

from subprocess import Popen, PIPE

def string_to_2_procs_to_file(input_s, first_cmd, second_cmd, output_filename):
    with open(output_filename, 'wb') as out_f:
        p2 = Popen(second_cmd, stdin=PIPE, stdout=out_f)
        p1 = Popen(first_cmd, stdout=p2.stdin, stdin=PIPE)
        p1.communicate(input=bytes(input_s))
        p1.wait()
        p2.stdin.close()
        p2.wait()

che può essere chiamato così:

string_to_2_procs_to_file('input data', ['awk', '-f', 'script.awk'], ['sort'], 'output.txt')
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top