Executando instruções de várias linhas na linha de comando de uma linha?

197

Estou usando Python com -cpara executar um loop de uma linha, ou seja:

$ python -c "for r in range(10): print 'rob'"

Isso funciona bem. No entanto, se eu importar um módulo antes do loop for, recebo um erro de sintaxe:

$ python -c "import sys; for r in range(10): print 'rob'"
  File "<string>", line 1
    import sys; for r in range(10): print 'rob'
              ^
SyntaxError: invalid syntax

Alguma idéia de como isso pode ser corrigido?

É importante para mim ter isso como uma linha para que eu possa incluí-lo em um Makefile.

Martineau
fonte

Respostas:

182

você poderia fazer

echo -e "import sys\nfor r in range(10): print 'rob'" | python

ou sem tubos:

python -c "exec(\"import sys\nfor r in range(10): print 'rob'\")"

ou

(echo "import sys" ; echo "for r in range(10): print 'rob'") | python

ou a resposta do @ SilentGhost / a resposta do @ Crast

jspcal
fonte
A última opção funciona muito bem com python3 no Windows
Ian Ellis
1
@RudolfOlah espero que você saiba até agora, mas apenas para referência, você precisa embrulhar a declaração de impressão para python3 + versões como:python -c "exec(\"import sys\nfor r in range(10): print('rob')\")"
systrigger
@systrigger obrigado, eu perdi que acho que estava com pressa e não percebi que heh
89

esse estilo também pode ser usado em makefiles (e na verdade é usado com bastante frequência).

python - <<EOF
import sys
for r in range(3): print 'rob'
EOF

ou

python - <<-EOF
    import sys
    for r in range(3): print 'rob'
EOF

neste último caso, os caracteres de tabulação iniciais também são removidos (e algumas perspectivas estruturadas podem ser obtidas)

em vez de EOF pode suportar qualquer palavra de marcador que não apareça no documento aqui no início de uma linha (consulte também aqui documentos na página de manual do bash ou aqui ).

xorho
fonte
9
Agradável. Para também passar argumentos , basta colocá-los depois <<EOF. Observe, no entanto, que é melhor citar o delimitador - por exemplo, <<'EOF'- para proteger o conteúdo do documento aqui contra expansões iniciais do shell .
precisa saber é o seguinte
56

O problema não está na declaração de importação, mas em qualquer coisa antes do loop for. Ou, mais especificamente, qualquer coisa que apareça antes de um bloco embutido.

Por exemplo, tudo isso funciona:

python -c "import sys; print 'rob'"
python -c "import sys; sys.stdout.write('rob\n')"

Se importar uma declaração fosse um problema, isso funcionaria, mas não:

python -c "__import__('sys'); for r in range(10): print 'rob'"

Para o seu exemplo muito básico, você pode reescrevê-lo da seguinte maneira:

python -c "import sys; map(lambda x: sys.stdout.write('rob%d\n' % x), range(10))"

No entanto, as lambdas podem executar apenas expressões, não instruções ou várias instruções, portanto, você ainda pode não conseguir fazer o que deseja. No entanto, entre expressões de gerador, compreensão de lista, lambdas, sys.stdout.write, o "mapa" interno e alguma interpolação criativa de cadeias, você pode executar algumas linhas de comando poderosas.

A questão é: até onde você quer ir e em que momento não é melhor escrever um pequeno .pyarquivo que seu makefile executa?

Crast
fonte
31


- Para que essa resposta funcione também com o Python 3.x , printé chamado como uma função : no 3.x, apenas print('foo') funciona, enquanto o 2.x também aceita print 'foo'.
- Para uma perspectiva de plataforma cruzada que inclui o Windows , consulte a resposta útil do kxr .

Em bash, kshouzsh :

Use uma seqüência de caracteres com citação ANSI C ( $'...'), que permita usar \npara representar novas linhas que são expandidas para novas linhas reais antes que a cadeia seja passada para python:

python -c $'import sys\nfor r in range(10): print("rob")'

Observe as instruções \nentre importe forpara efetuar uma quebra de linha.

Para passar valores de variáveis ​​de shell para esse comando, é mais seguro usar argumentos e acessá-los através sys.argvdo script Python:

name='rob' # value to pass to the Python script
python -c $'import sys\nfor r in range(10): print(sys.argv[1])' "$name"

Veja abaixo uma discussão dos prós e contras do uso de uma cadeia de comandos de aspas duplas ( pré-processada por sequência de escape) com referências de variáveis ​​de shell incorporadas .

Para trabalhar com segurança com $'...'cadeias:

  • \Instâncias duplas no seu código-fonte original .
    • \<char>- sequências, tais como \nneste caso, mas também os suspeitos habituais, tais como \t, \r, \b- são expandidos por $'...'(ver man printfpara os escapes de suporte)
  • 'Instâncias de escape como \'.

Se você deve permanecer em conformidade com POSIX :

Use printfcom uma substituição de comando :

python -c "$(printf %b 'import sys\nfor r in range(10): print("rob")')"

Para trabalhar com segurança com esse tipo de string:

  • \Instâncias duplas no seu código-fonte original .
    • \<char>- sequências, tais como \nneste caso, mas também os suspeitos habituais, tais como \t, \r, \b- são expandidas por printf(ver man printfpara as sequências de escape suportado).
  • Passe uma sequência de aspas simples para printf %be escape aspas simples incorporadas como '\'' (sic).

    • O uso de aspas simples protege o conteúdo da string da interpretação pelo shell .

      • Dito isto, para scripts Python curtos (como neste caso), você pode usar uma cadeia de caracteres com aspas duplas para incorporar valores de variáveis ​​de shell em seus scripts - desde que esteja ciente das armadilhas associadas (veja o próximo ponto); por exemplo, o shell se expande $HOMEpara o diretório inicial do usuário atual. no seguinte comando:

        • python -c "$(printf %b "import sys\nfor r in range(10): print('rob is $HOME')")"
      • No entanto, a abordagem geralmente preferida é passar valores do shell por meio de argumentos e acessá-los via sys.argvem Python; o equivalente do comando acima é:

        • python -c "$(printf %b 'import sys\nfor r in range(10): print("rob is " + sys.argv[1])')" "$HOME"
    • Embora o uso de uma cadeia de caracteres com aspas duplas seja mais conveniente - ele permite que você use aspas simples incorporadas sem escape e aspas duplas incorporadas, pois \"- também torna a string sujeita a interpretação pelo shell , que pode ou não ser a intenção; $e `caracteres no código-fonte que não são destinados ao shell podem causar um erro de sintaxe ou alterar a sequência inesperadamente.

      • Além disso, o próprio \processamento do shell em seqüências de citação dupla pode atrapalhar; por exemplo, para que o Python produza saída literal ro\b, você deve passar ro\\bpara ele; com uma '...'string de shell e instâncias duplicadas \ , obtemos:
        python -c "$(printf %b 'import sys\nprint("ro\\\\bs")')" # ok: 'ro\bs'
        Por outro lado, isso não funciona como planejado com uma "..."string de shell:
        python -c "$(printf %b "import sys\nprint('ro\\\\bs')")" # !! INCORRECT: 'rs'
        o shell interpreta ambos "\b" e "\\b"como literal \b, exigindo um número estonteante de \instâncias adicionais para obter o efeito desejado:
        python -c "$(printf %b "import sys\nprint('ro\\\\\\\\bs')")"

Para passar o código porstdin meio de -c:

Nota: Estou focando em soluções de linha única aqui; A resposta do xorho mostra como usar um documento aqui com várias linhas - no entanto, não deixe de citar o delimitador; por exemplo, a <<'EOF'menos que você queira explicitamente que o shell expanda a corda na frente (que acompanha as advertências mencionadas acima).


Em bash, kshouzsh :

Combine uma string ( $'...') com citação ANSI C ( ) com uma string here ( <<<...):

python - <<<$'import sys\nfor r in range(10): print("rob")'

-diz pythonexplicitamente para ler a partir de stdin (o que faz por padrão). -é opcional neste caso, mas se você também deseja passar argumentos para os scripts, é necessário desambiguar o argumento de um nome de arquivo de script:

python - 'rob' <<<$'import sys\nfor r in range(10): print(sys.argv[1])'

Se você deve permanecer compatível com POSIX :

Use printfcomo acima, mas com um pipeline para passar sua saída via stdin:

printf %b 'import sys\nfor r in range(10): print("rob")' | python

Com um argumento:

printf %b 'import sys\nfor r in range(10): print(sys.argv[1])' | python - 'rob'
mklement0
fonte
2
Essa deve ser a resposta eleita!
debuti
1
Isso também funciona e é provavelmente a melhor resposta, pois inclui uma explicação completa, bravo!
22

Alguma idéia de como isso pode ser corrigido?

Seu problema é criado pelo fato de que as instruções Python, separadas por ;, só podem ser "pequenas instruções", que são todas de uma linha. No arquivo de gramática nos documentos do Python :

stmt: simple_stmt | compound_stmt
simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
small_stmt: (expr_stmt | del_stmt | pass_stmt | flow_stmt |
             import_stmt | global_stmt | nonlocal_stmt | assert_stmt)

Instruções compostas não podem ser incluídas na mesma linha com outras instruções por ponto e vírgula - portanto, fazer isso com o -csinalizador se torna muito inconveniente.

Ao demonstrar o Python em um ambiente de shell bash, acho muito útil incluir instruções compostas. A única maneira simples de fazer isso de forma confiável é com heredocs (uma coisa de shell posix).

Heredocs

Use um heredoc (criado com <<) e do Python opção de interface de linha de comando , -:

$ python - <<-"EOF"
        import sys                    # 1 tab indent
        for r in range(10):           # 1 tab indent
            print('rob')              # 1 tab indent and 4 spaces
EOF

Adicionar o -depois <<(o <<-) permite que você use as guias para recuar (Stackoverflow converte as guias em espaços, por isso recuei 8 espaços para enfatizar isso). As guias principais serão removidas.

Você pode fazer isso sem as guias com apenas <<:

$ python - << "EOF"
import sys
for r in range(10):
    print('rob')
EOF

Colocar aspas EOFevita a expansão de parâmetros e aritmética . Isso torna o heredoc mais robusto.

Bash strings multilinha

Se você usar aspas duplas, obterá expansão do shell:

$ python -c "
> import sys
> for p in '$PATH'.split(':'):
>     print(p)
> "
/usr/sbin
/usr/bin
/sbin
/bin
...

Para evitar a expansão do shell, use aspas simples:

$ python -c '
> import sys
> for p in "$PATH".split(":"):
>     print(p)
> '
$PATH

Observe que precisamos trocar os caracteres de citação nos literais em Python - basicamente não podemos usar o caractere de citação que está sendo interpretado pelo BASH. Podemos alterná-los, como podemos em Python - mas isso já parece bastante confuso, e é por isso que não recomendo:

$ python -c '
import sys
for p in "'"$PATH"'".split(":"):
    print(p)
'
/usr/sbin
/usr/bin
/sbin
/bin
...

Crítica da resposta aceita (e outras)

Isso não é muito legível:

echo -e "import sys\nfor r in range(10): print 'rob'" | python

Não é muito legível e, além disso, difícil de depurar no caso de um erro:

python -c "exec(\"import sys\\nfor r in range(10): print 'rob'\")"

Talvez um pouco mais legível, mas ainda muito feio:

(echo "import sys" ; echo "for r in range(10): print 'rob'") | python

Você terá um mau momento se estiver "no seu python:

$ python -c "import sys
> for r in range(10): print 'rob'"

Não abuse mapou liste as compreensões para obter loops for:

python -c "import sys; map(lambda x: sys.stdout.write('rob%d\n' % x), range(10))"

Tudo isso é triste e ruim. Não os faça.

Aaron Hall
fonte
3
++ para informações gramaticais; o documento aqui (não expansível) é útil e a solução mais robusta, mas obviamente não é uma linha. Se uma solução de uma única linha é uma obrigação, usando uma cadeia ANSI C-citado ( bash, kshou zsh) é uma solução razoável: python -c $'import sys\nfor r in range(10): print(\'rob\')'(você só precisa se preocupar em escapar aspas simples (que você pode evitar utilizando aspas duplas) e barras invertidas).
precisa saber é o seguinte
14

basta usar return e digite-o na próxima linha:

user@host:~$ python -c "import sys
> for r in range(10): print 'rob'"
rob
rob
...
SilentGhost
fonte
6
Sério, você vai torcer alguma coisa se continuar fazendo isso. python $(srcdir)/myscript.pypor grande justiça.
Jason Orendorff
10

$ python2.6 -c "import sys; [sys.stdout.write('rob\n') for r in range(10)]"

Funciona bem. Use "[]" para alinhar seu loop for.

Pierre Pericard
fonte
9

O problema não está na importdeclaração. O problema é que as instruções de fluxo de controle não funcionam embutidas em um comando python. Substitua essa importdeclaração por qualquer outra e você verá o mesmo problema.

Pense nisso: python não pode incorporar tudo. Ele usa indentação para agrupar o controle de fluxo.

David Berger
fonte
7

Se o seu sistema for compatível com Posix.2, ele deverá fornecer o printfutilitário:

$ printf "print 'zap'\nfor r in range(3): print 'rob'" | python
zap
rob
rob
rob
Alex Martelli
fonte
2
Boa solução, mas sugiro usar printf %b '...' | pythonpara maior robustez, porque impede a printfinterpretação de seqüências como %d(especificadores de formato) na string de entrada. Além disso, a menos que você queira aplicar explicitamente expansões de shell à sua cadeia de comando Python, o que pode ser confuso, é melhor usar '(aspas simples) para a citação externa, para evitar as expansões e o processamento de folga que o shell aplica. para cadeias de citação dupla.
usar o seguinte comando
5

single/double quotese em backslashtodo lugar:

$ python -c 'exec("import sys\nfor i in range(10): print \"bob\"")'

Muito melhor:

$ python -c '
> import sys
> for i in range(10):
>   print "bob"
> '
kev
fonte
tão. Muito de. Melhor.
Marc
4

(respondeu em 23 de novembro de 2010 às 19:48) Eu não sou realmente um grande Pythoner - mas encontrei essa sintaxe uma vez, esqueci de onde, então pensei em documentá-la:

se você usar em sys.stdout.writevez de print( a diferença é que sys.stdout.writeaceita argumentos como uma função, entre parênteses - printe não usa ), então para uma linha, você pode inverter a ordem do comando e forremover o ponto e vírgula, e colocando o comando entre colchetes, ou seja:

python -c "import sys; [sys.stdout.write('rob\n') for r in range(10)]"

Não tenho idéia de como essa sintaxe seria chamada em Python :)

Espero que isto ajude,

Felicidades!


(EDIT terça-feira, 9 de abril, 20:57:30 2013) Bem, acho que finalmente encontrei o significado desses colchetes em uma linha; eles são "compreensões de lista" (aparentemente); observe primeiro no Python 2.7:

$ STR=abc
$ echo $STR | python -c "import sys,re; a=(sys.stdout.write(line) for line in sys.stdin); print a"
<generator object <genexpr> at 0xb771461c>

Portanto, o comando entre colchetes / parênteses é visto como um "objeto gerador"; se "iterarmos" chamando next()- então o comando entre parênteses será executado (observe o "abc" na saída):

$ echo $STR | python -c "import sys,re; a=(sys.stdout.write(line) for line in sys.stdin); a.next() ; print a"
abc
<generator object <genexpr> at 0xb777b734>

Se agora usarmos colchetes - observe que não precisamos chamar next()para que o comando seja executado, ele será executado imediatamente após a atribuição; no entanto, uma inspeção posterior revela que aé None:

$ echo $STR | python -c "import sys,re; a=[sys.stdout.write(line) for line in sys.stdin]; print a"
abc
[None]

Isso não deixa muita informação para procurar, no caso de colchetes - mas me deparei com esta página, que acho que explica:

Dicas e truques para Python - Primeira edição - Tutoriais em Python | Dream.In.Code :

Se você se lembra, o formato padrão de um gerador de linha única é um tipo de loop 'for' de uma linha entre colchetes. Isso produzirá um objeto iterável 'one-shot', que é um objeto que você pode repetir em apenas uma direção e que não pode ser reutilizado quando chegar ao fim.

Uma 'compreensão de lista' parece quase o mesmo que um gerador comum de uma linha, exceto que os colchetes regulares - () - são substituídos por colchetes - []. O principal avanço da compreensão alista é que produz uma 'lista', em vez de um objeto iterável 'one-shot', para que você possa ir e voltar através dele, adicionar elementos, classificar etc.

E, de fato, é uma lista - é apenas o primeiro elemento que não se torna assim que é executado:

$ echo $STR | python -c "import sys,re; print [sys.stdout.write(line) for line in sys.stdin].__class__"
abc
<type 'list'>
$ echo $STR | python -c "import sys,re; print [sys.stdout.write(line) for line in sys.stdin][0]"
abc
None

As compreensões da lista estão documentadas em 5. Estruturas de Dados: 5.1.4. Compreensões de lista - documentação do Python v2.7.4 como "Compreensões de lista fornecem uma maneira concisa de criar listas"; presumivelmente, é aí que a limitada "executabilidade" das listas entra em jogo em uma linha.

Bem, espero não estar muito errado aqui ...

EDIT2: e aqui está uma linha de comando de uma linha com dois for-loops não aninhados; ambos colocados entre colchetes "compreensão da lista":

$ echo $STR | python -c "import sys,re; a=[sys.stdout.write(line) for line in sys.stdin]; b=[sys.stdout.write(str(x)) for x in range(2)] ; print a ; print b"
abc
01[None]
[None, None]

Observe que a segunda "lista" bagora tem dois elementos, já que seu loop for foi executado explicitamente duas vezes; no entanto, o resultado de sys.stdout.write()ambos os casos foi (aparentemente) None.

sdaau
fonte
4

Essa variante é mais portátil para colocar scripts de várias linhas na linha de comando no Windows e * nix, py2 / 3, sem pipes:

python -c "exec(\"import sys \nfor r in range(10): print('rob') \")"

(Nenhum dos outros exemplos vistos aqui até agora o fez)

Neat no Windows é:

python -c exec"""import sys \nfor r in range(10): print 'rob' """
python -c exec("""import sys \nfor r in range(10): print('rob') """)

Limpo no bash / * nix é:

python -c $'import sys \nfor r in range(10): print("rob")'

Essa função transforma qualquer script de multilinha em um comando-liner portátil:

def py2cmdline(script):
    exs = 'exec(%r)' % re.sub('\r\n|\r', '\n', script.rstrip())
    print('python -c "%s"' % exs.replace('"', r'\"'))

Uso:

>>> py2cmdline(getcliptext())
python -c "exec('print \'AA\tA\'\ntry:\n for i in 1, 2, 3:\n  print i / 0\nexcept:\n print \"\"\"longer\nmessage\"\"\"')"

A entrada foi:

print 'AA   A'
try:
 for i in 1, 2, 3:
  print i / 0
except:
 print """longer
message"""
kxr
fonte
++ para o ângulo de plataforma cruzada e o conversor. Seu primeiro comando é tão bom quanto em termos de portabilidade (deixando o PowerShell de lado), mas, em última análise, não existe uma sintaxe única e totalmente robusta entre plataformas, porque a necessidade de usar aspas duplas corre o risco de expansões indesejadas do shell, com Windows que requer escape de caracteres diferentes dos shells do tipo POSIX. No PowerShell v3 ou superior, você pode fazer com que suas linhas de comando funcionem, inserindo a opção "parar de analisar" --%antes do argumento da cadeia de comando.
precisa saber é o seguinte
@ mklement0 " expansões de shell indesejadas ": bem, a inserção de expansões de shell em algo como print "path is %%PATH%%"resp. print "path is $PATH"geralmente é a opção que se deseja em um script ou linha de comando - a menos que se escape das coisas normalmente para a plataforma. Mesmo com outros idiomas. (A própria sintaxe do Python não sugere regularmente o uso de% e $ 's de maneira competitiva.)
KXR
Se você inserir referências de variável de shell diretamente no código-fonte Python, por definição não será portátil. Meu ponto é que mesmo se você em vez construída referências específicas da plataforma em distintas variáveis que você passar como argumentos para um single, "free-shell" de comando Python, que pode nem sempre trabalho, porque você não pode proteger a string entre aspas portably : por exemplo, e se você precisar literalmente $foo no seu código Python? Se você escapar como \$foopara o benefício de cartuchos semelhantes ao POSIX, cmd.exeainda verá o extra \ . Pode ser raro, mas vale a pena conhecer.
precisa saber é o seguinte
Tentando fazer isso no Windows PowerShell, mas o problema é que o python -c exec ("" "..." "") não produz nenhuma saída, não importa se o código em ... pode ser executado ou não; Eu poderia colocar qualquer tagarelice lá, e o resultado seria o mesmo. Eu sinto que o shell está "comendo" os fluxos stdout e stderr - como posso fazê-lo cuspir?
Yury
1

Quando eu precisava fazer isso, eu uso

python -c "$(echo -e "import sys\nsys.stdout.write('Hello World!\\\n')")"

Observe a barra invertida tripla para a nova linha na instrução sys.stdout.write.

Devilholk
fonte
Isso funciona, mas desde que você está usando echo -e, o que é fora do padrão e requer bash, kshou zsh, você pode também usar uma $'...'corda diretamente, o que também simplifica a escapar:python -c $'import sys\nsys.stdout.write("Hello World!\\n")'
mklement0
Esta resposta obras, as outras respostas não funcionam para Python3
1

Eu queria uma solução com as seguintes propriedades:

  1. Legível
  2. Leia stdin para processar a saída de outras ferramentas

Ambos os requisitos não foram fornecidos nas outras respostas, então veja como ler stdin enquanto faz tudo na linha de comando:

grep special_string -r | sort | python3 <(cat <<EOF
import sys
for line in sys.stdin:
    tokens = line.split()
    if len(tokens) == 4:
        print("%-45s %7.3f    %s    %s" % (tokens[0], float(tokens[1]), tokens[2], tokens[3]))
EOF
)
usuario
fonte
0

existe mais uma opção, sys.stdout.write retorna None, que mantém a lista vazia

cat somefile.log | python -c "import sys; [linha por linha em sys.stdin se sys.stdout.write (linha * 2)]"

user993533
fonte
0

Se você não quiser tocar em stdin e simular como se tivesse passado "python cmdfile.py", faça o seguinte em um shell bash:

$ python  <(printf "word=raw_input('Enter word: ')\nimport sys\nfor i in range(5):\n    print(word)")

Como você pode ver, ele permite que você use stdin para ler dados de entrada. Internamente, o shell cria o arquivo temporário para o conteúdo do comando de entrada.

Thava
fonte
++ por não "esgotar" o stdin com o próprio script (embora -c "$(...)"faça o mesmo e seja compatível com POSIX); dar <(...)um nome à construção: substituição de processo ; também funciona em kshe zsh.
precisa saber é o seguinte