在Jupyter Notebook中运行交互式命令行代码

49

Ipython Jupyter Notebook中有一个有趣的选项,可以直接从笔记本中执行命令行语句。例如:

! mkdir ...
! python file.py
此代码还可以使用os运行。
import os
os.system('cmd command')

但是如何运行交互式的shell命令呢?例如:

!conda install package

可能需要未来的输入 ([Y]/N) 或文件夹位置,但不会接受进一步的输入。


快速问题:您在询问哪个操作系统?对于这个问题,我认为很重要。显然,如果您使用的是Windows系统,即使您可以进入Bash shell,也不会有太大的区别。 - Dylan Brams
我猜这对他们三个都相关,但我特别关注Mac OS系统。 - Dimgold
Mac 可能有 bash shell。我明天会查一下。 - Dylan Brams
6个回答

18

!command语法是%system魔术的另一种语法,其文档可以在这里找到。

正如你所猜测的那样,它调用了os.system,只要os.system工作正常,就没有简单的方法可以知道将要运行的进程是否需要用户输入。因此,在使用笔记本电脑或任何多进程前端时,您无法动态地向正在运行的程序提供输入(与Python中调用input不同,我们可以拦截)。

由于您明确表达了从笔记本安装软件包的兴趣,因此我建议阅读来自Jake Van Der Plas的以下内容,它总结了对该主题的最近讨论,并解释了其中的一些复杂性。当然,您也可以选择conda的--yes选项,但这并不能保证使用conda安装始终有效。

请注意,!command是一个IPython功能,而不是Jupyter功能。


12
假设你询问的是互动性方面的问题,那么有一些你可以尝试的方法。
如果你曾经想知道Jupyter如何知道单元格输出何时结束:实际上它并不知道,它只是将捕获的任何输出转储到最近活动的单元格中。
import threading,time
a=5
threading.Thread(target=lambda:[print(a),time.sleep(20),print(a)]).start()
< p >(这只是附加信息,意在简化示例)当20秒等待运行时,您有时间通过发出a=6来激活另一个单元格。

这可用于将某些控制台代码的输出显示在屏幕上,同时从主线程进行控制:

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()

然后您可以发出命令,例如:
pin.write(b'ls -l\n')
pin.flush()

并且

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

即使是b'ls\nexit\n'也可以工作,这就是为什么outLoop如此之长的原因(一个简单的while(proc.poll() is None)-print(...)循环会在抓取所有输出之前就结束)。

然后整个过程可以自动化处理为:

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

这在https://try.jupyter.org/上运行良好,但我显然不想在那里尝试安装conda包,所以当conda询问时我不知道会发生什么。

一个幸运的事情是输入字段停留在单元格底部(使用ls; sleep 10; ls进行测试)。不幸的是,输入字段需要在末尾添加额外的条目才能消失(如果它只是简单的while(...)-write(bytearray(input()))-flush() 3行代码,就无法成功退出)。

如果有人想在Windows上尝试这个,请使用'cmd',但我建议硬编码'windows-1252'而不是sys.stdin/out.encoding:虽然他们说是UTF-8,但仅仅执行一个dir命令就已经产生了既不是UTF-8也不是ASCII的输出(大小中间的不间断空格是0xA0字符)。或者只需删除decode部分(并使用running=0xA in line)。


感谢您提供详细的答案。是否有办法让这个机制在 Jupyter 中为交互式 shell 脚本(根据用户提供的选项工作)工作? - instinct246

9

对于我遇到的大多数命令,使用非交互式参数是一个不错的选择。例如,在上述情况下:

conda install package -y

如果你必须要提供提示信息,你可以使用printf hack,例如:

printf 'y\n' | conda install package

这支持多个输入,您可以通过'\n'进行分隔


从上面的答案中看起来并不像它本可以这么清楚,但最重要的部分已经在那里了。要自动批准在 Jupyter 笔记本中安装 conda 包,您将使用 -y 参数。因此,在(IPython)Jupyter 中使用 conda 安装包的方式是在单元格中键入:!conda -y install package - burkesquires
这个回答并不能完全回答这个问题,但它解决了我的使用情境并且很可能也适用于其他大多数情况。 - ritmatter

1

这对我来说似乎是有效的

import sys
!{sys.executable} -m pip install panda

enter image description here


3
现在有现代的魔术命令可用于 notebook 中的 pip 和 conda 包管理器,详情请见此处。正如 Matt 的回答所指出的那样,您所发布的所有内容最好都用 %pip install panda 命令来替代。现在更加简单易用了。然而,OP问题实际上是关于 conda 的 -y--yes 选项,对于这个问题,Matt 的高票答案已经指出可以使用 %conda install 魔术命令。因此,在一个 cell 中这句话是有效的:%conda install scipy -y。 - Wayne
pip 的中文等价物是什么?谢谢。 - Florida Man

1
我将其作为答案发布。这不是一个好的答案,但我处理这个问题的方式是编写一个在后台运行的bash脚本。我研究了'!'运算符并没有太多的文档资料。我甚至在Jupyter源代码中找不到它。这篇文章建议:[Safari book about Jupyter Predecessor and component IPython][1]https://www.safaribooksonline.com/blog/2014/02/12/using-shell-commands-effectively-ipython/,这可能只是以前的事情,并且可能会一直保持这样。除非你想要黑入Jupyter Notebook的Magic Commands部分并自己修复它。话虽如此,考虑到通过一点bash编程(它简单而专注),你可以做到你想做的事情,你可能会考虑这条路。特别是如果你需要足够好的结果来证明自己。
如果你想了解如何运行bash脚本并获得预期的响应,这个答案就是你要找的:使用bash脚本回答交互提示

1
我知道这是一个旧帖子,但我没有看到提到这一点:
!yes | conda install package

这会将“y”输入以回答任何待处理的“y/n?”问题。显然,要小心使用 - 只有当您绝对确定“是”是正确答案时才使用。

2
请考虑使用反引号格式化终端命令或代码。另外,如果您能解释该命令的作用,那就更好了。 - Sarabadu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接