Por que esse comando bash não é executado quando chamado por python?

3

O comando

$ find ~/foo/ -type f -iname "*.txt" -print0 | parallel -0 cat

usa o GNU Parallel para imprimir todos os arquivos .txt sob ~/foo/ .

Eu tenho um script python no qual desejo chamar este comando bash:

import subprocess, sys

def runBashCommand(my_command):
    process = subprocess.Popen(my_command.split(), stdout=subprocess.PIPE)
    output  = process.communicate()[0]
    return None

def makeCommand(my_path):
    return "find {} -type f -iname \"*.txt\" -print0 | parallel -0 cat".format(my_path)

Emitindo

>>> makeCommand('~/foo/')

retorna

'find ~/foo/ -type f -iname "*.txt" -print0 | parallel -0 cat'

mas emitindo

>>> runBashCommand(makeCommand('~/foo/'))

produz o erro

find: paths must precede expression: |
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]

qual é o problema com o meu script?

    
por Brian Fitzpatrick 12.05.2016 / 20:39

2 respostas

4

Você não está realmente executando um comando bash . O que você está fazendo é executar um executável diretamente e passar argumentos.

Tente o seguinte script para ver o que está acontecendo:

import subprocess
p = subprocess.Popen(["echo", "a", "b", "|", "rev"], stdout=subprocess.PIPE)
print p.communicate()

A saída será:

('a b | rev\n', None)

Não há redirecionamento acontecendo, o "|" está sendo passado literalmente. Ou seja, é como se você tivesse digitado find ... \| parallel ... . Assim, o erro.

Existem duas maneiras de corrigir isso.

  • O caminho mais fácil: passe shell=True para subprocess.Popen . Isso será executado através da casca, com tudo o que isso implica. Se você fizer isso, você também precisará passar uma string em vez de um array:

    import subprocess
    p = subprocess.Popen("echo a b | rev", stdout=subprocess.PIPE, shell=True)
    print p.communicate()
    
    # Result: ('b a\n', None)
    

    Se você fizer isso, tenha muito cuidado com a substituição de argumentos em sua string .

  • A maneira robusta: abra dois processos usando o Python e conecte-os juntos.

    import subprocess
    # First command
    p1 = subprocess.Popen(["echo", "a", "b"], stdout=subprocess.PIPE)
    # Second command's input linked to the first one's output
    p2 = subprocess.Popen(["rev"], stdin=p1.stdout, stdout=subprocess.PIPE)
    # Read from p2 to get the output
    print p2.communicate()
    
    # Result: ('b a\n', None)
    

    Isso é mais robusto e não gera uma casca extra, mas, por outro lado, é mais digitação. Se você fizer isso, observe que nenhuma substituição de shell acontece . No seu caso, não parece que você precisa, mas se você quiser usar, por exemplo, ~ , você terá que fazer isso através do Python (por exemplo, os.getenv("HOME") ).

por 12.05.2016 / 21:05
0

Você não pode split() da sua sequência de comandos, pois contém caracteres que precisam ser processados pelo shell, por exemplo, ~ e | . Use a versão:

process = subprocess.Popen(my_command, stdout=subprocess.PIPE, shell=True)
    
por 12.05.2016 / 20:57