Esecuzione di istruzioni su più righe nella riga di comando su una riga?


197

Sto usando Python con -cper eseguire un ciclo one-liner, ovvero:

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

Funziona benissimo. Tuttavia, se importare un modulo prima del ciclo for, ottengo un errore di sintassi:

$ 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

Qualche idea su come risolvere questo problema?

È importante per me avere questo come una linea in modo da poterlo includere in un Makefile.


Risposte:


182

potresti fare

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

o senza tubi:

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 risposta di @ SilentGhost / la risposta di @ Crast


L'ultima opzione funziona alla grande con python3 in Windows
Ian Ellis il

1
@RudolfOlah spero che tu lo sappia ormai ma solo per riferimento, devi avvolgere l'istruzione di stampa per le versioni di python3 + come:python -c "exec(\"import sys\nfor r in range(10): print('rob')\")"
systrigger

@systrigger grazie, mi sono perso che penso di avere fretta e non mi sono reso conto che heh

89

questo stile può essere usato anche nei makefile (e in effetti è usato abbastanza spesso).

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

in quest'ultimo caso vengono rimossi anche i caratteri di tabulazione principali (e si possono ottenere alcune prospettive strutturate)

invece di EOF può contenere qualsiasi marcatore che non appare nel documento qui all'inizio di una riga (vedere anche i documenti qui nella manpage bash o qui ).


9
Bello. Per passare anche argomenti , è sufficiente posizionarli dopo <<EOF. Si noti, tuttavia, che è meglio citare il delimitatore, ad esempio, in <<'EOF'modo da proteggere il contenuto del documento qui dalle espansioni della shell iniziale .
mklement0

56

Il problema non è in realtà con l'istruzione import, è con qualcosa che sta prima del ciclo for. O più specificamente, tutto ciò che appare prima di un blocco inline.

Ad esempio, funzionano tutti:

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

Se l'importazione essendo una dichiarazione fosse un problema, questo funzionerebbe, ma non:

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

Per il tuo esempio molto semplice, puoi riscriverlo come questo:

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

Tuttavia, lambda può solo eseguire espressioni, non istruzioni o più istruzioni, quindi potresti non essere ancora in grado di fare ciò che vuoi fare. Tuttavia, tra le espressioni del generatore, la comprensione dell'elenco, lambdas, sys.stdout.write, la "mappa" incorporata e alcune interpolazioni creative di stringhe, è possibile eseguire alcune potenti operazioni a riga singola.

La domanda è: fino a che punto vuoi andare, e in quale momento non è meglio scrivere un piccolo .pyfile che il tuo makefile esegue invece?


31


- Per far funzionare questa risposta anche con Python 3.x , printviene chiamato come una funzione : in 3.x, funziona solo print('foo') , mentre anche 2.x accetta print 'foo'.
- Per una prospettiva multipiattaforma che include Windows , vedere la risposta utile di kxr .

In bash, kshozsh :

Utilizzare una stringa tra virgolette C ANSI ( $'...'), che consente di utilizzare \nper rappresentare le nuove righe che vengono espanse in nuove righe effettive prima che la stringa venga passata a python:

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

Nota le istruzioni \ntra importe forper effettuare un'interruzione di riga.

Per passare valori di variabili di shell a tale comando, è più sicuro usare argomenti e accedervi tramite sys.argvlo 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"

Vedi sotto per una discussione sui pro e contro dell'uso di una stringa di comando (escape sequenza preelaborata) con virgolette doppie con riferimenti a variabili shell incorporate .

Per lavorare in sicurezza con le $'...'stringhe:

  • Doppie \ istanze nel codice sorgente originale .
    • \<char>sequenze - come \nin questo caso, ma anche i soliti noti come \t, \r, \b- sono espanse da $'...'(vedi man printfper le fughe supportati)
  • Escape 'istanze come \'.

Se è necessario rimanere conformi a POSIX :

Utilizzare printfcon una sostituzione comando :

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

Per lavorare in sicurezza con questo tipo di stringa:

  • Doppie \ istanze nel codice sorgente originale .
    • \<char>sequenze - come \nin questo caso, ma anche i soliti noti quali \t, \r, \b- sono espansi da printf(vedi man printfper le sequenze di escape supportati).
  • Passare un singolo citato stringa printf %be fuggire apici incorporati come '\'' (sic).

    • L'uso di virgolette singole protegge il contenuto della stringa dall'interpretazione da parte della shell .

      • Detto questo, per gli script Python brevi (come in questo caso) è possibile utilizzare una stringa tra virgolette doppie per incorporare i valori delle variabili shell nei propri script - purché si conoscano le insidie ​​associate (vedere il punto successivo); ad esempio, la shell si espande $HOMEnella home directory dell'utente corrente. nel seguente comando:

        • python -c "$(printf %b "import sys\nfor r in range(10): print('rob is $HOME')")"
      • Tuttavia, l'approccio generalmente preferito è passare valori dalla shell tramite argomenti e accedervi tramite sys.argvin Python; l'equivalente del comando sopra è:

        • python -c "$(printf %b 'import sys\nfor r in range(10): print("rob is " + sys.argv[1])')" "$HOME"
    • Mentre usare una stringa tra virgolette è più conveniente - ti permette di usare virgolette singole incorporate senza caratteri di escape e virgolette doppie incorporate come \"- rende anche la stringa soggetta all'interpretazione da parte della shell , che potrebbe essere o meno l'intento; $e i `caratteri nel codice sorgente non destinati alla shell possono causare un errore di sintassi o alterare la stringa in modo imprevisto.

      • Inoltre, l' \elaborazione della shell in stringhe tra virgolette doppie può interferire; per esempio, per fare in modo che Python produca un output letterale ro\b, devi passarci ro\\bsopra; con una '...'stringa guscio e raddoppiato \ casi, si ottiene:
        python -c "$(printf %b 'import sys\nprint("ro\\\\bs")')" # ok: 'ro\bs'
        Per contro, questo non non funzionano come previsto con una "..."stringa shell:
        python -c "$(printf %b "import sys\nprint('ro\\\\bs')")" # !! INCORRECT: 'rs'
        Le interpreta guscio sia "\b" e "\\b"come letterale \b, richiedono una serie vertiginosa di ulteriori \istanze per ottenere l'effetto desiderato:
        python -c "$(printf %b "import sys\nprint('ro\\\\\\\\bs')")"

Per passare il codice tramitestdin anziché -c:

Nota: mi sto concentrando su soluzioni a linea singola qui; La risposta di xorho mostra come usare un documento multi-riga qui - assicurati di citare il delimitatore, comunque; ad esempio, a <<'EOF'meno che non si desideri esplicitamente che la shell espanda la stringa in avanti (che viene fornita con le avvertenze sopra riportate).


In bash, kshozsh :

Combina una stringa tra virgolette C ANSI ( $'...') con una stringa qui ( <<<...):

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

-dice pythonesplicitamente di leggere dallo stdin (cosa che fa di default). -è facoltativo in questo caso, ma se si desidera anche passare argomenti agli script, è necessario per chiarire l'argomento da un nome file di script:

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

Se è necessario rimanere conformi a POSIX :

Utilizzare printfcome sopra, ma con una pipeline in modo da passare il suo output tramite stdin:

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

Con un argomento:

printf %b 'import sys\nfor r in range(10): print(sys.argv[1])' | python - 'rob'

2
Questa dovrebbe essere la risposta eletta!
debuti il

1
Questo funziona anche ed è probabilmente la risposta migliore poiché include una spiegazione completa, bravo!

22

Qualche idea su come risolvere questo problema?

Il tuo problema è creato dal fatto che le istruzioni Python, separate da ;, possono essere solo "piccole dichiarazioni", che sono tutte a linea singola. Dal file di grammatica nei documenti 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)

Le istruzioni composte non possono essere incluse sulla stessa riga con altre istruzioni tramite punto e virgola, quindi farlo con la -cbandiera diventa molto scomodo.

Durante la dimostrazione di Python in un ambiente shell bash, trovo molto utile includere istruzioni composte. L'unico modo semplice per farlo in modo affidabile è con heredocs (una cosa con shell posix).

Heredocs

Utilizzare un heredoc (creato con <<) e di Python opzione di interfaccia a riga di 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

Aggiungendo il -dopo <<(il <<-) è possibile utilizzare le schede per rientrare (Stackoverflow converte le schede in spazi, quindi ho indentato 8 spazi per enfatizzarlo). Le schede principali verranno rimosse.

Puoi farlo senza le schede con solo <<:

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

Mettere le virgolette intorno EOFimpedisce l' espansione dei parametri e dell'aritmetica . Questo rende l'eredoc più robusto.

Stringhe multilinea di Bash

Se usi le virgolette doppie, otterrai l'espansione della shell:

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

Per evitare l'espansione della shell utilizzare virgolette singole:

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

Nota che dobbiamo scambiare i caratteri delle virgolette sui valori letterali in Python: fondamentalmente non possiamo usare i caratteri delle virgolette interpretati da BASH. Possiamo alternarli però, come possiamo in Python - ma questo sembra già abbastanza confuso, motivo per cui non lo consiglio:

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

Critica della risposta accettata (e di altri)

Questo non è molto leggibile:

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

Non molto leggibile e inoltre difficile da eseguire il debug in caso di errore:

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

Forse un po 'più leggibile, ma ancora abbastanza brutto:

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

Avrai un brutto momento se hai "nel tuo pitone:

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

Non abusare mapo elencare le comprensioni per ottenere loop for:

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

Questi sono tutti tristi e cattivi. Non farli.


3
++ per le informazioni grammaticali; il qui-doc (non espandibile) è pratico e la soluzione più robusta, ma ovviamente non una linea. Se una soluzione single-line è un must, utilizzando una stringa ANSI C-citato ( bash, ksh, o zsh) è una soluzione ragionevole: python -c $'import sys\nfor r in range(10): print(\'rob\')'(dovrete solo preoccuparvi di fuga virgolette singole (che si può evitare utilizzando le virgolette) e backslash).
mklement0

14

basta usare return e digitarlo nella riga successiva:

user@host:~$ python -c "import sys
> for r in range(10): print 'rob'"
rob
rob
...

6
Seriamente, ti slogerai qualcosa se continui a farlo. python $(srcdir)/myscript.pyper grande giustizia.
Jason Orendorff l'

10

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

Funziona bene. Usa "[]" per incorporare il tuo ciclo for.


9

Il problema non è con l' importaffermazione. Il problema è che le istruzioni del flusso di controllo non funzionano allineate in un comando python. Sostituisci quella importdichiarazione con qualsiasi altra istruzione e vedrai lo stesso problema.

Pensaci: Python non può probabilmente incorporare tutto. Usa il rientro per raggruppare il flusso di controllo.


7

Se il tuo sistema è conforme a Posix.2, dovrebbe fornire l' printfutilità:

$ printf "print 'zap'\nfor r in range(3): print 'rob'" | python
zap
rob
rob
rob

2
Buona soluzione, ma suggerisco di usarlo printf %b '...' | pythonper maggiore robustezza, perché impedisce printfdi interpretare sequenze come %d(identificatori di formato) nella stringa di input. Inoltre, a meno che non si desideri applicare esplicitamente le espansioni della shell alla stringa di comando Python in primo piano (che può creare confusione), è meglio usare '(virgolette singole) per le quotazioni esterne, per evitare sia le espansioni che l'elaborazione del gioco che la shell applica alle stringhe tra virgolette doppie.
mklement0

5

single/double quotese backslashovunque:

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

Molto meglio:

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

così. tanto. meglio.
Marc,

4

(ha risposto il 23 novembre 10 alle 19:48) Non sono un grande Pythoner, ma ho trovato questa sintassi una volta, ho dimenticato da dove, quindi ho pensato di documentarlo:

se usi sys.stdout.writeinvece di print( la differenza è, sys.stdout.writeprende argomenti come una funzione, tra parentesi - mentre printnon lo fa ), quindi per una riga, puoi cavartela invertendo l'ordine del comando e forrimuovendo il punto e virgola, e racchiudendo il comando tra parentesi quadre, ovvero:

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

Non ho idea di come questa sintassi verrebbe chiamata in Python :)

Spero che questo ti aiuti,

Saluti!


(EDIT mar 9 aprile 20:57:30 2013) Beh, penso di aver finalmente trovato di cosa si tratta questa parentesi quadra in una riga; sono "comprensione delle liste" (apparentemente); prima nota questo in 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>

Quindi il comando tra parentesi tonde / parentesi è visto come un "oggetto generatore"; se "iteriamo" attraverso di essa chiamando next()- quindi verrà eseguito il comando tra parentesi (notare "abc" nell'output):

$ 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 ora utilizziamo parentesi quadre - nota che non è necessario chiamare next()per eseguire il comando, che viene eseguito immediatamente dopo l'assegnazione; tuttavia, un'ispezione successiva rivela che aè None:

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

Questo non lascia molte informazioni da cercare, nel caso delle parentesi quadre - ma mi sono imbattuto in questa pagina che penso spiega:

Python Tips And Tricks - Prima edizione - Python Tutorials | Codice Dream.In :

Se ricordi, il formato standard di un generatore a linea singola è una specie di loop "for" all'interno di parentesi. Questo produrrà un oggetto iterabile "one-shot" che è un oggetto su cui puoi scorrere in una sola direzione e che non puoi riutilizzare una volta raggiunta la fine.

Una "comprensione della lista" sembra quasi uguale a un normale generatore di una riga, tranne per il fatto che le parentesi regolari - () - sono sostituite da parentesi quadre - []. Il principale vantaggio della comprensione globale è che produce un 'elenco', piuttosto che un oggetto iterabile 'one-shot', in modo da poter andare avanti e indietro, aggiungere elementi, ordinare, ecc.

E infatti è un elenco - è solo il suo primo elemento che diventa nessuno non appena viene eseguito:

$ 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

La comprensione dell'elenco è altrimenti documentata in 5. Strutture di dati: 5.1.4. Comprensioni elenco: la documentazione di Python v2.7.4 come "Comprensioni elenco fornisce un modo conciso per creare elenchi"; presumibilmente, è qui che entra in gioco la limitata "eseguibilità" delle liste.

Beh, spero di non essere terribilmente troppo fuori dal segno qui ...

EDIT2: ed ecco una riga di comando di un liner con due for-loop non nidificati; entrambi racchiusi tra parentesi quadre "comprensione elenco":

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

Si noti che il secondo "elenco" bora ha due elementi, poiché il suo ciclo for è stato esplicitamente eseguito due volte; tuttavia, il risultato di sys.stdout.write()in entrambi i casi è stato (apparentemente) None.


4

Questa variante è la più portatile per mettere script a più righe sulla riga di comando su Windows e * nix, py2 / 3, senza pipe:

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

(Nessuno degli altri esempi visti qui finora lo ha fatto)

Ordinato su 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') """)

Neat su bash / * nix è:

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

Questa funzione trasforma qualsiasi script multilinea in un comando-one-liner portatile:

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\"\"\"')"

L'input è stato:

print 'AA   A'
try:
 for i in 1, 2, 3:
  print i / 0
except:
 print """longer
message"""

++ per l'angolo multipiattaforma e il convertitore. Il tuo primo comando è buono quanto arriva in termini di portabilità (lasciando PowerShell da parte), ma alla fine non esiste una sintassi singola, completamente solida e multipiattaforma, perché la necessità di utilizzare virgolette doppie comporta il rischio di espansioni indesiderate della shell, con Windows richiede l'escaping di caratteri diversi rispetto alle shell simili a POSIX. In PowerShell v3 o versione successiva puoi far funzionare le tue righe di comando inserendo l'opzione "stop-parsing" --%prima dell'argomento della stringa di comando.
mklement0

@ mklement0 " espansioni di shell indesiderate ": bene, l'inserimento di espansioni di shell in qualcosa come print "path is %%PATH%%"resp. print "path is $PATH"di solito è l'opzione che si desidera in uno script o in una riga di comando, a meno che non si evadano le cose come al solito per la piattaforma. Lo stesso con altre lingue. (La stessa sintassi di Python non suggerisce regolarmente l'uso di% e $ in modo concorrenziale.)
kxr

Se inserisci riferimenti a variabili di shell direttamente nel codice sorgente di Python, per definizione non sarà portatile. Il mio punto è che anche se invece hai costruito riferimenti specifici della piattaforma in variabili separate che passi come argomenti a un singolo comando Python "privo di shell" , che potrebbe non funzionare sempre, perché non puoi proteggere la stringa tra virgolette in modo portabile : ad esempio, cosa succede se hai bisogno letterale $foo nel tuo codice Python? Se lo sfuggi come \$fooper il beneficio di shell simili a POSIX, cmd.exevedrai ancora il extra \ . Può essere raro ma vale la pena conoscerlo.
mklement0

Provando a farlo in Windows PowerShell, ma il problema è che python -c exec ("" "..." "") non produce alcun output, indipendentemente dal fatto che il codice in ... sia in grado di essere eseguito o no; Potrei mettere qualcosa di incomprensibile lì, e il risultato sarebbe lo stesso. Sento che il guscio sta "mangiando" sia i flussi stdout che stderr - come posso farli sputare?
Yury

2

Questo script fornisce un'interfaccia da riga di comando simile al Perl:

Pyliner - Script per eseguire codice Python arbitrario sulla riga di comando (ricetta Python)


Penso che volessero qualcosa per risolvere questo problema, non per usare un altro strumento. Comunque bel suggerimento
enrico.bacis

Sono d'accordo con @ enrico.bacis, ma sono ancora felice che tu abbia aggiunto questa risposta. Risponde alla domanda che ho avuto quando ho cercato su Google questa pagina.
martedì

1

Quando avevo bisogno di farlo, lo uso

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

Nota la barra rovesciata tripla per la nuova riga nell'istruzione sys.stdout.write.


Funziona, ma dal momento che stai usando echo -e, che non è standard e richiede bash, kshoppure zsh, puoi anche usare $'...'direttamente una stringa, il che semplifica anche la fuga:python -c $'import sys\nsys.stdout.write("Hello World!\\n")'
mklement0

Questa risposta funziona, le altre risposte non funzionano per Python3

1

Volevo una soluzione con le seguenti proprietà:

  1. Leggibile
  2. Leggi stdin per l'elaborazione dell'output di altri strumenti

Entrambi i requisiti non sono stati forniti nelle altre risposte, quindi ecco come leggere stdin mentre si fa tutto sulla riga di 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
)

0

c'è un'altra opzione, sys.stdout.write restituisce None, che mantiene vuoto l'elenco

cat somefile.log | python -c "import sys; [riga per riga in sys.stdin se sys.stdout.write (riga * 2)]"


0

Se non vuoi toccare stdin e simulare come se avessi passato "python cmdfile.py", puoi fare quanto segue da una shell bash:

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

Come puoi vedere, ti permette di usare stdin per leggere i dati di input. Internamente la shell crea il file temporaneo per il contenuto del comando di input.


++ per non "usare" stdin con lo script stesso (anche se -c "$(...)"fa lo stesso ed è conforme a POSIX); dare <(...)un nome al costrutto: sostituzione del processo ; funziona anche in kshe zsh.
mklement0
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.