¿Ejecutar sentencias de varias líneas en la línea de comandos de una línea?

197

Estoy usando Python con -cpara ejecutar un bucle de una línea, es decir:

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

Esto funciona bien Sin embargo, si importo un módulo antes del ciclo for, obtengo un error de sintaxis:

$ 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

¿Alguna idea de cómo se puede solucionar esto?

Es importante para mí tener esto como una sola línea para poder incluirlo en un Makefile.

Martineau
fuente

Respuestas:

182

Podrías hacerlo

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

o sin tuberías:

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

o

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

o la respuesta de @ SilentGhost / @ La respuesta de Crast

jspcal
fuente
La última opción funciona muy bien con python3 en Windows
Ian Ellis
1
@RudolfOlah espero que ya lo sepas, pero solo como referencia, debes envolver la declaración de impresión para las versiones de python3 + como:python -c "exec(\"import sys\nfor r in range(10): print('rob')\")"
systrigger
@systrigger gracias, me perdí que creo que tenía prisa y no me di cuenta de eso je
89

Este estilo también se puede usar en archivos MAKE (y, de hecho, se usa con bastante frecuencia).

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

o

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

en este último caso, los caracteres de tabulación principales también se eliminan (y se puede lograr una perspectiva estructurada)

en lugar de EOF puede soportar cualquier palabra de marcador que no aparezca en el documento aquí al comienzo de una línea (vea también los documentos aquí en la página de manual de bash o aquí ).

xorho
fuente
9
Agradable. Para pasar también argumentos , simplemente colóquelos después <<EOF. Sin embargo, tenga en cuenta que es mejor citar el delimitador, por ejemplo, <<'EOF'para proteger el contenido del documento aquí de las expansiones de shell iniciales.
mklement0
56

El problema no es en realidad con la declaración de importación, es con cualquier cosa que esté antes del ciclo for. O más específicamente, cualquier cosa que aparezca antes de un bloque en línea.

Por ejemplo, todo esto funciona:

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

Si importar ser una declaración fuera un problema, esto funcionaría, pero no:

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

Para su ejemplo muy básico, podría reescribirlo así:

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

Sin embargo, lambdas solo puede ejecutar expresiones, no declaraciones o declaraciones múltiples, por lo que es posible que aún no pueda hacer lo que desea hacer. Sin embargo, entre las expresiones generadoras, la comprensión de la lista, lambdas, sys.stdout.write, el "mapa" integrado y alguna interpolación creativa de cadenas, puede hacer algunas frases potentes.

La pregunta es, ¿hasta dónde quieres llegar y en qué punto no es mejor escribir un .pyarchivo pequeño que ejecuta tu archivo MAKE?

Crast
fuente
31


- Para que esta respuesta también funcione con Python 3.x , printse llama como una función : en 3.x, solo print('foo') funciona, mientras que 2.x también acepta print 'foo'.
- Para una perspectiva multiplataforma que incluye Windows , consulte la útil respuesta de kxr .

En bash, kshozsh :

Use una cadena entre comillas C ANSI ( $'...'), que permite usar \npara representar nuevas líneas que se expanden a nuevas líneas reales antes de pasar la cadena a python:

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

Tenga en cuenta las declaraciones \nentre importy forpara efectuar un salto de línea.

Para pasar valores de variables de shell a dicho comando, es más seguro usar argumentos y acceder a ellos a través sys.argvdel script de Python:

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

Vea a continuación una discusión sobre los pros y los contras del uso de una cadena de comando de comillas dobles ( preprocesado por secuencia de escape) con referencias de variables de shell incrustadas .

Para trabajar de forma segura con $'...'cuerdas:

  • \Instancias dobles en su código fuente original .
    • \<char>secuencias - como \nen este caso, sino también los sospechosos habituales, tales como \t, \r, \b- se expanden por $'...'(ver man printfpara los escapes compatibles)
  • 'Instancias de escape como \'.

Si debe seguir cumpliendo con POSIX :

Usar printfcon una sustitución de comando :

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

Para trabajar de forma segura con este tipo de cadena:

  • \Instancias dobles en su código fuente original .
    • \<char>secuencias - como \nen este caso, sino también los sospechosos habituales, tales como \t, \r, \b- se expanden por printf(véase man printfpara las secuencias de escape compatibles).
  • Pasar una entre comillas simples cadena a printf %by escapar comillas simples incrustadas como '\'' (sic).

    • El uso de comillas simples protege el contenido de la cadena de la interpretación del shell .

      • Dicho esto, para los scripts Python cortos (como en este caso) puede usar una cadena de comillas dobles para incorporar valores variables de shell en sus scripts, siempre y cuando conozca las dificultades asociadas (vea el siguiente punto); por ejemplo, el shell se expande $HOMEal directorio de inicio del usuario actual. en el siguiente comando:

        • python -c "$(printf %b "import sys\nfor r in range(10): print('rob is $HOME')")"
      • Sin embargo, el enfoque generalmente preferido es pasar valores desde el shell a través de argumentos y acceder a ellos a través sys.argvde Python; El equivalente del comando anterior es:

        • python -c "$(printf %b 'import sys\nfor r in range(10): print("rob is " + sys.argv[1])')" "$HOME"
    • Si bien el uso de una cadena de comillas dobles es más conveniente : le permite usar comillas simples incrustadas sin comillas y comillas dobles incrustadas como \", también hace que la cadena esté sujeta a interpretación por parte del shell , lo que puede o no ser la intención; $y los `caracteres en su código fuente que no están destinados al shell pueden causar un error de sintaxis o alterar la cadena inesperadamente.

      • Además, el propio \procesamiento del shell en cadenas de doble comilla puede interferir; por ejemplo, para que Python produzca una salida literal ro\b, debe pasarle ro\\b; con una '...'cadena de shell e instancias duplicadas \ , obtenemos:
        python -c "$(printf %b 'import sys\nprint("ro\\\\bs")')" # ok: 'ro\bs'
        Por el contrario, esto no funciona según lo previsto con una "..."cadena de shell:
        python -c "$(printf %b "import sys\nprint('ro\\\\bs')")" # !! INCORRECT: 'rs'
        el shell interpreta ambos "\b" y "\\b"como literal \b, lo que requiere un número vertiginoso de \instancias adicionales para lograr el efecto deseado:
        python -c "$(printf %b "import sys\nprint('ro\\\\\\\\bs')")"

Para pasar el código a través de enstdin lugar de -c:

Nota: Me estoy enfocando en soluciones de línea única aquí; La respuesta de xorho muestra cómo usar un documento de varias líneas aquí ; sin embargo, asegúrese de citar el delimitador; por ejemplo, a <<'EOF'menos que desee explícitamente que el shell expanda la cadena por adelantado (que viene con las advertencias mencionadas anteriormente).


En bash, kshozsh :

Combine una cadena entre comillas C ANSI ( $'...') con una cadena aquí ( <<<...):

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

-le dice pythonexplícitamente que lea desde stdin (lo que hace por defecto). -es opcional en este caso, pero si también desea pasar argumentos a los scripts, lo necesita para desambiguar el argumento de un nombre de archivo de script:

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

Si debe seguir cumpliendo con POSIX :

Use printfcomo se indica arriba, pero con una tubería para pasar su salida a través de stdin:

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

Con un argumento:

printf %b 'import sys\nfor r in range(10): print(sys.argv[1])' | python - 'rob'
mklement0
fuente
2
¡Esta debería ser la respuesta elegida!
debut el
1
Esto también funciona y es probablemente la mejor respuesta, ya que incluye una explicación completa, ¡bravo!
22

¿Alguna idea de cómo se puede solucionar esto?

Su problema se crea por el hecho de que las declaraciones de Python, separadas por ;, solo pueden ser "declaraciones pequeñas", que son todas simples. Del archivo de gramática en los documentos de 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)

Las declaraciones compuestas no se pueden incluir en la misma línea con otras declaraciones a través de punto y coma, por lo que hacer esto con la -cbandera se vuelve muy inconveniente.

Cuando demuestro Python en un entorno bash shell, me resulta muy útil incluir declaraciones compuestas. La única forma sencilla de hacer esto de manera confiable es con heredocs (una cosa de shell posix).

Heredocs

Use un heredoc (creados con <<) y de Python opción de interfaz de línea de comandos , -:

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

Agregar -after <<(the <<-) le permite usar pestañas para sangrar (Stackoverflow convierte las pestañas en espacios, así que he sangrado 8 espacios para enfatizar esto). Las pestañas principales se eliminarán.

Puedes hacerlo sin las pestañas con solo <<:

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

Poner comillas EOFevita la expansión de parámetros y aritmética . Esto hace que el heredoc sea más robusto.

Bash cuerdas multilínea

Si usa comillas dobles, obtendrá la expansión de shell:

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

Para evitar la expansión de shell, use comillas simples:

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

Tenga en cuenta que necesitamos intercambiar los caracteres de comillas en los literales en Python; básicamente, no podemos usar caracteres de comillas interpretados por BASH. Sin embargo, podemos alternarlos, como podemos hacerlo en Python, pero esto ya parece bastante confuso, por eso no recomiendo esto:

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

Crítica de la respuesta aceptada (y otras)

Esto no es muy legible:

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

No muy legible, y además difícil de depurar en caso de error:

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

Quizás un poco más legible, pero aún bastante feo:

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

Tendrás un mal momento si tienes "en tu pitón:

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

No abuses mapni enumeres las comprensiones para obtener for-loops:

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

Todos estos son tristes y malos. No los hagas.

Aaron Hall
fuente
3
++ para la información gramatical; here-doc (sin expansión) es útil y la solución más robusta, pero obviamente no es una línea. Si una solución de una sola línea es imprescindible, usar una cadena entre comillas ANSI C ( bash, ksho zsh) es una solución razonable: python -c $'import sys\nfor r in range(10): print(\'rob\')'(solo tendrá que preocuparse por escapar de las comillas simples (que puede evitar usando comillas dobles) y barras invertidas).
mklement0
14

solo usa return y escríbelo en la siguiente línea:

user@host:~$ python -c "import sys
> for r in range(10): print 'rob'"
rob
rob
...
SilentGhost
fuente
66
En serio, vas a torcer algo si sigues haciendo esto. python $(srcdir)/myscript.pypor gran justicia.
Jason Orendorff
10

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

Funciona bien. Use "[]" para alinear su bucle for.

Pierre Pericard
fuente
9

El problema no es con la importdeclaración. El problema es que las declaraciones de flujo de control no funcionan en línea en un comando de Python. Reemplace esa importdeclaración con cualquier otra declaración y verá el mismo problema.

Piénselo: Python no puede en línea todo. Utiliza sangría para agrupar el flujo de control.

David Berger
fuente
7

Si su sistema es compatible con Posix.2, debe proporcionar la printfutilidad:

$ printf "print 'zap'\nfor r in range(3): print 'rob'" | python
zap
rob
rob
rob
Alex Martelli
fuente
2
Buena solución, pero sugiero usar printf %b '...' | pythonpara mayor robustez, ya que evita printfinterpretar secuencias como %d(especificadores de formato) en la cadena de entrada. Además, a menos que desee explícitamente aplicar expansiones de shell a la cadena de comandos de Python por adelantado (lo que puede ser confuso), es mejor usar '(comillas simples) para las comillas externas, para evitar tanto las expansiones como el procesamiento de reacción que aplica el shell. a cadenas entre comillas dobles.
mklement0
5

single/double quotesy en backslashtodas partes:

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

Mucho mejor:

$ python -c '
> import sys
> for i in range(10):
>   print "bob"
> '
kev
fuente
entonces. mucho. mejor.
Marc
4

(respondió el 23 de noviembre de 2010 a las 19:48) No soy realmente un gran Pythoner, pero una vez encontré esta sintaxis, olvidé de dónde, así que pensé en documentarla:

si usa en sys.stdout.writelugar de print( la diferencia es, sys.stdout.writetoma argumentos como una función, entre paréntesis, mientras printque no lo hace ), entonces, para una línea, puede salirse invirtiendo el orden del comando y for, quitando el punto y coma, y encerrando el comando entre corchetes, es decir:

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

No tengo idea de cómo se llamaría esta sintaxis en Python :)

Espero que esto ayude,

¡Salud!


(EDITAR el martes 9 de abril 20:57:30 2013) Bueno, creo que finalmente encontré de qué se tratan estos corchetes en una sola línea; son "listas de comprensiones" (aparentemente); primera nota esto en 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>

Entonces el comando entre paréntesis / paréntesis se ve como un "objeto generador"; si "iteramos" a través de él al llamar next(), se ejecutará el comando dentro del paréntesis (observe el "abc" en la salida):

$ 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>

Si ahora usamos corchetes, tenga en cuenta que no necesitamos llamar next()para ejecutar el comando, se ejecuta inmediatamente después de la asignación; sin embargo, una inspección posterior revela que aes None:

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

Esto no deja mucha información para buscar, para el caso de los corchetes, pero me topé con esta página que creo explica:

Consejos y trucos de Python - Primera edición - Tutoriales de Python | Dream.In.Code :

Si recuerda, el formato estándar de un generador de una sola línea es una especie de bucle 'for' de una línea dentro de los corchetes. Esto producirá un objeto iterativo 'one-shot' que es un objeto sobre el que puede iterar en una sola dirección y que no puede reutilizar una vez que llegue al final.

Una 'comprensión de lista' se ve casi igual que un generador de una línea regular, excepto que los corchetes regulares (() se reemplazan por corchetes - []. El mayor avance de la comprensión de la lista es que produce una 'lista', en lugar de un objeto iterable 'de una sola vez', para que pueda ir y venir a través de él, agregar elementos, ordenar, etc.

Y, de hecho, es una lista: es solo que su primer elemento se convierte en ninguno tan pronto como se ejecuta:

$ 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

Las comprensiones de las listas se documentan en 5. Estructuras de datos: 5.1.4. Lista de comprensiones: la documentación de Python v2.7.4 como "Las comprensiones de listas proporcionan una forma concisa de crear listas"; presumiblemente, ahí es donde entra en juego la limitada "ejecubilidad" de las listas en una sola línea.

Bueno, espero no estar demasiado fuera de lugar aquí ...

EDIT2: y aquí hay una línea de comando de una línea con dos bucles for no anidados; ambos encerrados entre corchetes de "comprensión de la 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 la segunda "lista" bahora tiene dos elementos, ya que su ciclo for se ejecutó explícitamente dos veces; sin embargo, el resultado de sys.stdout.write()en ambos casos fue (aparentemente) None.

sdaau
fuente
4

Esta variante es más portátil para colocar scripts de varias líneas en la línea de comandos en Windows y * nix, py2 / 3, sin canalizaciones:

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

(Ninguno de los otros ejemplos vistos aquí hasta ahora lo hizo)

Neat en Windows es:

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

Neat on bash / * nix es:

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

Esta función convierte cualquier secuencia de comandos multilínea en una línea de comando 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\"\"\"')"

La entrada fue:

print 'AA   A'
try:
 for i in 1, 2, 3:
  print i / 0
except:
 print """longer
message"""
kxr
fuente
++ para el ángulo multiplataforma y el convertidor. Su primer comando es tan bueno como lo es en términos de portabilidad (dejando a un lado PowerShell), pero en última instancia no existe una sintaxis multiplataforma completamente robusta , porque la necesidad de usar comillas dobles conlleva el riesgo de expansiones de shell no deseadas, con Windows que requiere el escape de caracteres diferentes a los shells similares a POSIX. En PowerShell v3 o superior, puede hacer que sus líneas de comando funcionen insertando la opción "detener el análisis" --%antes del argumento de la cadena de comandos.
mklement0
@ mklement0 " expansiones de concha no deseadas ": bueno, la inserción de expansiones de concha en algo como print "path is %%PATH%%"resp. print "path is $PATH"Por lo general, es la opción que se desea en un script o línea de comandos, a menos que se escapen las cosas como de costumbre para la plataforma. Lo mismo con otros idiomas. (La sintaxis de Python en sí no sugiere regularmente el uso de% y $ 's de manera competitiva).
kxr
Si inserta referencias de variables de shell directamente en el código fuente de Python, por definición no será portátil. Mi punto es que, incluso si en vez construiste referencias específicas de la plataforma en distintas variables que se pasa como argumento a una sola, "cáscara libre" de comandos Python, que puede no siempre el trabajo, porque no se puede proteger la cadena entre comillas dobles portable : por ejemplo, ¿qué pasa si necesita literal$foo en su código de Python? Si escapas en \$foobeneficio de los proyectiles tipo POSIX,cmd.exe aún verá el extra \ . Puede ser raro, pero vale la pena saberlo.
mklement0
Intento hacer esto en Windows PowerShell, pero el problema es que python -c exec ("" "..." "") no produce ningún resultado, sin importar si el código en ... puede ejecutarse o no; Podría poner cualquier galimatías allí, y el resultado sería el mismo. Siento que el caparazón está "comiendo" las corrientes stdout y stderr, ¿cómo puedo hacer que las escupe?
Yury
1

Cuando necesitaba hacer esto, uso

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

Tenga en cuenta la triple barra diagonal inversa para la nueva línea en la declaración sys.stdout.write.

Devilholk
fuente
Esto funciona, pero dado que está usando echo -e, lo que no es estándar y requiere bash, ksho bien zsh, también puede usar una $'...'cadena directamente, lo que también simplifica el escape:python -c $'import sys\nsys.stdout.write("Hello World!\\n")'
mklement0
Esta respuesta funciona, las otras respuestas no funcionan para Python3
1

Quería una solución con las siguientes propiedades:

  1. Legible
  2. Leer stdin para procesar la salida de otras herramientas

Ambos requisitos no se proporcionaron en las otras respuestas, así que aquí está cómo leer stdin mientras se hace todo en la línea 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
)
Mella
fuente
0

hay una opción más, sys.stdout.write devuelve None, que mantiene la lista vacía

cat somefile.log | python -c "import sys; [línea para línea en sys.stdin if sys.stdout.write (línea * 2)]"

usuario993533
fuente
0

Si no desea tocar stdin y simular como si hubiera pasado "python cmdfile.py", puede hacer lo siguiente desde un shell bash:

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

Como puede ver, le permite usar stdin para leer datos de entrada. Internamente, el shell crea el archivo temporal para el contenido del comando de entrada.

Thava
fuente
++ por no "usar" stdin con el script en sí (aunque -c "$(...)"hace lo mismo y es compatible con POSIX); para dar <(...)un nombre al constructo: sustitución del proceso ; También funciona en kshy zsh.
mklement0