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.