web-dev-qa-db-fra.com

Exécution du code de ligne de commande à partir du bloc-notes Jupyter

Il existe une option intéressante dans Ipython Jupyter Notebook pour exécuter des instructions en ligne de commande directement à partir du bloc-notes. Par exemple:

! mkdir ...
! python file.py

De plus, ce code peut être exécuté en utilisant os:

import os
os.system('cmd command')

mais comment puis-je exécuter des commandes interactives Shell. Par exemple:

!conda install package

peut nécessiter une entrée future ([Y]/N) ou l'emplacement du dossier, mais n'acceptera aucune autre entrée.

11
Dimgold

En supposant que vous posiez des questions sur l'interactivité, vous pouvez essayer quelque chose.

Si vous vous êtes déjà demandé comment Jupyter sait quand la sortie d'une cellule se termine: eh bien, apparemment, il ne le sait pas, il ne fait que vider toute sortie capturée dans la dernière cellule active:

import threading,time
a=5
threading.Thread(target=lambda:[print(a),time.sleep(20),print(a)]).start()

(Exemple délibérément plus court que Nice, car il ne s'agit que d'informations secondaires. Pendant l'attente de 20 secondes, vous avez le temps d'activer une autre cellule, par exemple en émettant un a=6).

Cela peut être utilisé pour obtenir la sortie d'un code de console à l'écran, tout en le contrôlant depuis le thread principal:

import sys,threading,subprocess

proc=subprocess.Popen('/bin/sh',stdout=subprocess.PIPE,stdin=subprocess.PIPE,stderr=subprocess.STDOUT)
pout=proc.stdout
pin=proc.stdin

def outLoop():
    running=True
    while(running):
        line=pout.readline().decode(sys.stdout.encoding)
        print(line,end='')
        running='\n' in line
    print('Finished')

threading.Thread(target=outLoop).start()

Ensuite, vous pouvez émettre des commandes, comme

pin.write(b'ls -l\n')
pin.flush()

et

pin.write(b'exit\n')
pin.flush()

Même si b'ls\nexit\n' fonctionne, c'est pourquoi outLoop est si long (une simple boucle _while(proc.poll() is None)-print(...) se terminera plus tôt qu'elle n'a saisi toutes les sorties.

Ensuite, le tout peut être automatisé comme:

while(proc.poll() is None):
    inp=bytearray(input('something: ')+'\n',sys.stdin.encoding)
    if(proc.poll() is None):
        pin.write(inp)
        pin.flush()

Cela fonctionne bien sur https://try.jupyter.org/ , mais évidemment, je ne voulais pas essayer d'installer les paquets conda là-bas, donc je ne sais pas ce qui se passe lorsque conda pose une question.

Une chance est que le champ de saisie reste au bas de la cellule (testé avec ls;sleep 10;ls). Un malheur est que le champ d’entrée a besoin d’une entrée supplémentaire à la fin pour disparaître (et c’est déjà la bonne façon, quand c’était un simple while(...)-write(bytearray(input())) -flush() 3-liner, il sortait avec une exception.

Si quelqu'un veut essayer ceci sous Windows, cela fonctionne avec 'cmd', mais je suggère d'utiliser un 'windows-1252' codé en dur au lieu de sys.stdin/out.encoding: ils disent UTF-8, mais une simple commande dir produit déjà une sortie qui n'est ni UTF-8 ni ASCII (l’espace insécable entre les groupes de 3 chiffres des tailles est un caractère 0xA0). Ou alors supprimez simplement la partie decode (et utilisez running=0xA in line)

1
tevemadar

la !commandsyntax est une syntaxe alternative de la magie %system, dans laquelle la documentation peut être trouvée ici

Comme vous l'avez deviné, l'appel de os.system et pour autant que os.system fonctionne, il n'existe pas de moyen simple de savoir si le processus que vous allez exécuter nécessitera l'intervention de l'utilisateur. Ainsi, lorsque vous utilisez le bloc-notes ou une interface multi-processus, vous n’avez aucun moyen de fournir de manière dynamique des entrées au programme que vous exécutez. (contrairement à un appel à input en Python, nous pouvons intercepter).

Comme vous manifestez un intérêt particulier pour l’installation de paquets à partir du cahier, je vous suggère de lire le texte suivant de Jake Van Der Plas , qui résume une discussion récente sur le sujet, et d’expliquer certaines des complications que cela entraîne. Vous pouvez bien sûr utiliser l'option --yes de conda, mais cela ne garantit pas que l'installation avec conda fonctionnera toujours. 

Notez également que !command est une fonctionnalité IPython, pas une fonctionnalité Jupyter.

3
Matt

Une bonne option pour la plupart des commandes que j'ai rencontrées consiste à utiliser des arguments non interactifs. Par exemple. dans le cas ci-dessus:

conda install package -y

Si vous avez absolument besoin de nourrir les invites, vous pouvez utiliser printf hack, par exemple:

printf 'y\n' | conda install package

Cela prend en charge plusieurs entrées, vous les séparez par '\ n'

0
Ivan

Je poste ceci comme réponse. Ce n'est pas une bonne réponse, mais la façon dont je gérerais le problème consiste à écrire un script bash à exécuter en arrière-plan. J'ai regardé dans le '!' opérateur et il ne semble pas avoir beaucoup de documentation. Je ne peux même pas le trouver dans la source Jupyter. Cet article:

[Livre Safari sur le prédécesseur Jupyter et le composant IPython] [1] https://www.safaribooksonline.com/blog/2014/02/12/using-Shell-commands-effectively-ipython/

suggère que c'est simplement comme cela que les choses étaient avant et probablement pour toujours. Sauf si vous souhaitez pirater la partie Commandes magiques du Bloc-notes Jupyter et la réparer vous-même.

Cela dit, étant donné qu'avec une petite programmation bash (c'est simple et ciblé), vous pouvez faire ce que vous essayez de faire, vous pourriez envisager cette voie. Surtout si vous avez besoin de résultats suffisants pour y mettre de la réputation.

Si vous souhaitez utiliser des scripts bash avec une réponse attendue, répondez à cette question: Demandez à script bash de répondre aux invites interactives

0
Dylan Brams