web-dev-qa-db-fra.com

Exécuter des instructions multilignes dans la ligne de commande d’une ligne?

J'utilise Python avec -c pour exécuter une boucle à une ligne, à savoir:

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

Cela fonctionne bien. Cependant, si j'importe un module avant la boucle for, j'obtiens une erreur de syntaxe:

$ 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

Une idée de comment cela peut être corrigé?

C'est important pour moi d'avoir ceci comme une ligne unique afin de pouvoir l'inclure dans un Makefile.

168
user248237

vous pourriez faire

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

ou sans tuyaux:

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 @ réponse de SilentGhost / réponse de @ Crast

155
jspcal

ce style peut aussi être utilisé dans les makefiles (et en fait, il est utilisé assez souvent).

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

dans ce dernier cas, les caractères de tabulation menant sont également supprimés (et il est possible de créer des perspectives structurées)

au lieu de EOF peut supporter tout marqueur ne paraissant pas dans le document ici au début d'une ligne (voir aussi ici les documents dans la page de manuel bash ou ici ).

80
xorho

Le problème ne vient pas de la déclaration d'importation, mais de tout ce qui se trouve avant la boucle for. Ou plus précisément, tout ce qui apparaît avant un bloc en ligne.

Par exemple, ceux-ci fonctionnent tous:

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

Si l'importation était une déclaration posant problème, cela fonctionnerait, mais ce ne serait pas le cas:

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

Pour votre exemple très basique, vous pouvez le récrire comme ceci:

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

Cependant, lambdas ne peut exécuter que des expressions, et non des instructions ou des instructions multiples. Il est donc possible que vous ne puissiez toujours pas faire ce que vous voulez. Cependant, entre les expressions du générateur, la compréhension de la liste, les lambdas, sys.stdout.write, le "mappage" intégré et certaines interpolations de chaînes créatives, vous pouvez créer de puissants one-liners.

La question est de savoir jusqu'où voulez-vous aller et à quel moment ne vaut-il pas mieux écrire un petit .py fichier que votre makefile exécute à la place?

46
Crast


- Pour que cette réponse fonctionne avec Python 3.x, print est également appelé function: dans 3.x, seulementprint('foo') fonctionne, alors que 2.x accepte également print 'foo'.
- Pour une perspective multiplate-forme incluant Windows, voir réponse utile de kxr .

Dans bash, ksh ou zsh:

Utilisez un chaîne de caractères citée en C ANSI ($'...'), Qui permet d'utiliser \n pour représenter les nouvelles lignes étendues aux nouvelles lignes réelles avant que la chaîne ne soit transmise à python:

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

Notez le \n Entre les instructions import et for pour effectuer un saut de ligne.

Pour passer les valeurs de variable shell à une telle commande, il est plus sûr d'utiliser arguments et d'y accéder via sys.argv À l'intérieur du Python:

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

Voir ci-dessous pour une discussion des avantages et des inconvénients de l'utilisation d'une chaîne de commande (séquence d'échappement pré-traitée) double-guillemet avec embedded.

Pour travailler en toute sécurité avec les chaînes $'...':

  • Double\ Instances dans votre original code source.
    • Les séquences \<char> - telles que \n Dans ce cas, mais également les suspects habituels tels que \t, \r, \b - sont développées par $'...' (voir man printf pour les échappées prises en charge)
  • Échap une instance ' En tant que \'.

Si vous devez rester compatible POSIX :

Utilisez printf avec un substitution de commande :

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

Pour travailler en toute sécurité avec ce type de chaîne:

  • Double\ Instances dans votre original code source.
    • Les séquences \<char> - telles que \n Dans ce cas, mais également les suspects habituels tels que \t, \r, \b - sont développées by printf (voir man printf pour les séquences d'échappement supportées).
  • Passez une chaîne entre guillemets à printf %b Et échappez les guillemets simples sous forme de'\'' (Sic).

    • L'utilisation de guillemets simples protège le contenu de la chaîne de l'interprétation par le Shell.

      • Cela dit, pour short Python scripts (comme dans ce cas), vous pouvez utiliser une chaîne entre guillemets pour incorporer Shell = valeurs variables dans vos scripts - tant que vous connaissez les pièges associés (voir point suivant); Par exemple, le Shell étend $HOME au répertoire de base de l'utilisateur actuel. dans la commande suivante:

        • python -c "$(printf %b "import sys\nfor r in range(10): print('rob is $HOME')")"
      • Cependant, l'approche généralement recommandée consiste à transmettre les valeurs du shell via arguments et à y accéder via sys.argv En Python; l'équivalent de la commande ci-dessus est:

        • python -c "$(printf %b 'import sys\nfor r in range(10): print("rob is " + sys.argv[1])')" "$HOME"
    • Lorsque vous utilisez un double-guillemet, la chaîne est plus pratique - vous permet d’utiliser des guillemets simples non échappés et des guillemets doubles en tant que \" - cela rend également la chaîne sujette à interprétation par le Shell, ce qui peut être ou ne pas être l'intention; Les caractères $ Et ` De votre code source qui ne sont pas destinés au shell peuvent provoquer une erreur de syntaxe ou modifier la chaîne de manière inattendue.

      • De plus, le traitement \ Du shell dans les chaînes entre guillemets peut faire obstacle; par exemple, pour obtenir Python pour produire une sortie littérale ro\b, vous devez lui transmettre ro\\b; Avec une chaîne '...' Shell et des instances doublées\, nous obtenons:
        python -c "$(printf %b 'import sys\nprint("ro\\\\bs")')" # ok: 'ro\bs'
        En revanche, cela pas fonctionne comme prévu avec une chaîne de caractères "...":
        python -c "$(printf %b "import sys\nprint('ro\\\\bs')")" # !! INCORRECT: 'rs'
        Le shell interprète les deux"\b" Et "\\b" Comme un littéral \b, Nécessitant un nombre vertigineux de \ instances pour obtenir l'effet souhaité:
        python -c "$(printf %b "import sys\nprint('ro\\\\\\\\bs')")"

Pour transmettre le code via stdin plutôt que -c:

Remarque: je me concentre sur single - solutions en ligne ici; La réponse de xorho montre comment utiliser un multi-ligne here-document - assurez-vous de quote le délimiteur, cependant; Par exemple, <<'EOF', à moins que vous ne souhaitiez explicitement que le shell étende la chaîne à l’avant (ce qui vient avec les mises en garde mentionnées ci-dessus).


Dans bash, ksh ou zsh:

Combinez un chaîne de caractères citée en C ANSI ($'...') Avec un ici-chaîne (<<<...):

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

- Indique à python explicitement de lire à partir de stdin (ce qu'il fait par défaut). - Est facultatif dans ce cas, mais si vous souhaitez également transmettre arguments aux scripts, vous en aurez besoin pour désambiguïser l'argument d'un nom de fichier de script:

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

Si vous devez rester compatible POSIX :

Utilisez printf comme ci-dessus, mais avec un pipeline afin de transmettre sa sortie via stdin:

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

Avec un argument:

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

Une idée de comment cela peut être corrigé?

Votre problème est créé par le fait que les instructions Python, séparées par ;, ne peuvent être que de "petites déclarations", qui sont toutes des lignes simples. À partir du fichier de grammaire dans le documents 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)

Les instructions composées ne peuvent pas être incluses sur la même ligne avec d'autres instructions via des points-virgules - pour ce faire, utilisez le -c Le drapeau devient très gênant.

Lors de la démonstration de Python dans un environnement bash shell, je trouve très utile d'inclure des instructions composées. Le seul moyen simple de le faire est d'utiliser heredocs.

Heredocs

Utilisez un heredoc (créé avec <<) et Python option d'interface de ligne de commande , -:

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

Ajout du - après << (le <<-) vous permet d'utiliser des onglets pour indenter (Stackoverflow convertit les onglets en espaces, c'est pourquoi j'ai mis en retrait 8 espaces pour le souligner). Les onglets principaux seront dépouillés.

Vous pouvez le faire sans les onglets avec seulement <<:

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

Mettre des guillemets autour de EOF empêche paramètre et expansion arithmétique . Cela rend l'hérédoc plus robuste.

Critique de la réponse acceptée (et autres)

Ce n'est pas très lisible:

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

Pas très lisible, et en plus difficile à déboguer en cas d'erreur:

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

Peut-être un peu plus lisible, mais quand même assez moche:

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

Vous passerez un mauvais moment si vous avez " est dans votre python:

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

N'abusez pas de map ou ne faites pas la liste de compréhensions pour obtenir des boucles:

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

Ce sont tous tristes et méchants. Ne les fais pas.

13
Aaron Hall

utilisez simplement return et tapez-le à la ligne suivante:

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

Le problème ne vient pas de l'instruction import. Le problème est que les instructions de flux de contrôle ne fonctionnent pas en ligne dans une instruction python. Remplacez cette instruction import par une autre instruction et vous verrez le même problème.

Pensez-y: python ne peut pas tout insérer en ligne. Il utilise l'indentation pour regrouper le contrôle-flux.

8
David Berger

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

Fonctionne bien. Utilisez "[]" pour insérer votre boucle for.

7
Pierre Pericard

Si votre système est conforme à Posix.2, il devrait fournir l'utilitaire printf:

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

(répondit le 23 novembre 10 à 19:48) Je ne suis pas vraiment un gros Pythoner - mais j'ai trouvé cette syntaxe une fois, j'ai oublié d'où, alors j'ai pensé la documenter:

si vous utilisez sys.stdout.write au lieu de print (, la différence étant que sys.stdout.write prend les arguments comme une fonction, entre parenthèses - alors que print ne le fait pas ), alors, pour un one-liner, vous pouvez inverser l'ordre de la commande et le for, en supprimant le point-virgule et en y insérant la commande entre crochets, à savoir:

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

Je ne sais pas du tout comment cette syntaxe serait appelée dans Python :)

J'espère que cela t'aides,

À votre santé!


(EDIT mar 9 avr 20:57:30 2013) Eh bien, je pense avoir enfin trouvé le sens de ces crochets dans une ligne; ce sont des "compréhensions de liste" (apparemment); Notez d'abord ceci dans 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>

Ainsi, la commande entre parenthèses/parenthèses est considérée comme un "objet générateur"; si nous "itérons" à travers elle en appelant next() - alors la commande à l'intérieur de la parenthèse sera exécutée (notez le "abc" dans la sortie):

$ 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 nous utilisons maintenant des crochets - notons qu'il n'est pas nécessaire d'appeler next() pour que la commande s'exécute, elle s'exécute immédiatement lors de l'affectation; cependant, une inspection ultérieure révèle que a est None:

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

Cela ne laisse pas beaucoup d’informations à rechercher, pour le cas des crochets - mais je suis tombé par hasard sur cette page qui, je pense, explique:

Trucs et astuces Python - Première édition - Python Didacticiels | Dream.In.Code) :

Si vous vous en souvenez bien, le format standard d'un générateur de ligne unique est une sorte de ligne "pour" en boucle entre crochets. Cela produira un objet itératif 'one-shot' qui est un objet que vous pouvez parcourir dans une seule direction et que vous ne pouvez pas réutiliser une fois que vous avez atteint la fin.

Une "compréhension de liste" ressemble presque à un générateur régulier à une ligne, sauf que les crochets réguliers ) - sont remplacés par des crochets - []. L’avantage majeur de la compréhension de l’aliste est de produire une "liste" plutôt qu’un objet itératif "unique", de sorte que vous puissiez aller et venir à travers elle, ajouter des éléments, trier, etc.

Et en effet c'est une liste - c'est juste que son premier élément ne devient rien dès qu'il est exécuté:

$ 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

Les compréhensions de liste sont documentées autrement dans --- (5. Structures de données: 5.1.4. Compréhensions de liste - Python v2.7.4 en tant que "Les compréhensions de liste fournissent un moyen concis de créer des listes "; vraisemblablement, c’est là que" l’exécutabilité "limitée des listes entre en jeu dans les doublons.

Eh bien, espérons que je ne suis pas trop à l'écart ici ...

EDIT2: et voici une ligne de commande à une ligne avec deux boucles for non imbriquées; les deux sont placés entre crochets "compréhension de la liste":

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

Notez que la seconde "liste" b a maintenant deux éléments, puisque sa boucle for a été explicitement exécutée deux fois; Cependant, le résultat de sys.stdout.write() dans les deux cas était (apparemment) None.

4
sdaau

single/double quotes et backslash partout:

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

Beaucoup mieux:

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

Cette variante est la plus portable pour mettre des scripts multilignes en ligne de commande sous Windows et * nix, py2/3, sans les pipes:

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

(Aucun des autres exemples vus ici jusqu'à présent ne l'a fait)

Neat sur Windows c'est:

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 sur bash/* nix est:

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

Cette fonction transforme n'importe quel script multiligne en une commande portable:

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

Usage:

>>> 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'entrée était:

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

Ce script fournit une interface de ligne de commande semblable à Perl:

Pyliner - Script à exécuter de manière arbitraire Python sur la ligne de commande (recette Python))

2
Drew Gulino

Je voulais une solution avec les propriétés suivantes:

  1. Lisible
  2. Lire stdin pour le traitement de la sortie d'autres outils

Les deux conditions requises n'étant pas fournies dans les autres réponses, voici comment lire stdin en faisant tout ce qui est sur la ligne de commande:

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
)
1
Nick

Si vous ne voulez pas toucher stdin et simuler comme si vous aviez passé "python cmdfile.py", vous pouvez effectuer les opérations suivantes à partir d'un shell bash:

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

Comme vous pouvez le constater, cela vous permet d’utiliser stdin pour lire les données d’entrée. En interne, le shell crée le fichier temporaire pour le contenu de la commande d'entrée.

0
Thava

il y a une autre option, sys.stdout.write, qui ne renvoie aucune, ce qui permet de garder la liste vide.

 catmilefichier.log | python -c "importer sys; [ligne pour ligne dans sys.stdin si sys.stdout.write (ligne * 2)]" 
0
user993533

Quand j’avais besoin de faire ça, j’utilise

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

Notez la triple barre oblique inverse pour la nouvelle ligne dans l'instruction sys.stdout.write.

0
Devilholk