背景
jupyter ってとても便利ですよね。
python 書けて、markdown 書けて、シェルコマンド実行もできる。
でも、インフラ担当者的にはなかなか面倒な点もありまして。
ログを残さずにファイルの rm とか、編集とかサーバ内を弄れてしまいます。
会社だと操作ログ残らないとセキュリティ的にNGなんてことがあると思いますが
jupyter はまさにそれが出来てしまいます。
で、「jupyterからシェルコマンドを制御できないか?」という要望があったので
色々試してみたときの話を書きたいと思います。
環境
$ python -V
Python 3.6.4 :: Anaconda, Inc.
$ jupyter notebook -V
5.4.0
jupyter_notebook_config.py で設定
とりあえず設定でどうにかしようと試みます。
$ jupyter notebook --generate-config
$ vim ~/.jupyter/jupyter_notebook_config.py
最終行にこのように追記します。
c.NotebookApp.terminado_settings = {'shell_command':''}
保存後に jupyter notebook(lab)を起動すると、terminal を選択しても開かなくなっていると思われます。
notebook の os.system
や subprocess
からも実行できないっぽいです。

~補足~
jupyter notebook ver5.5.0 から terminals_enabled
が追加され、これを False にすると
「terminal」という選択肢が表示されなくなるようです。
https://blog.jupyter.org/jupyter-notebook-5-5-0-17b2e2d4f7f
c.NotebookApp.terminals_enabled = False
inputtransformer.py をいじる
喜ぶのはまだ早かった。このような方法で実行できてしまいます。
ということで、どうにかしようと探し回ったところ、
$ cd ~/.pyenv/versions/anaconda3-5.1.0/lib/python3.6/site-packages/IPython/core
$ vim inputtransformer.py
# -----------------------------------------------------------------------------
# Globals
# -----------------------------------------------------------------------------
# The escape sequences that define the syntax transformations IPython will
# apply to user input. These can NOT be just changed here: many regular
# expressions and other parts of the code may use their hardcoded values, and
# for all intents and purposes they constitute the 'IPython syntax', so they
# should be considered fixed.
ESC_SHELL = '!' # Send line to underlying system shell
ESC_SH_CAP = '!!' # Send line to system shell and capture output
ESC_HELP = '?' # Find information about object
ESC_HELP2 = '??' # Find extra-detailed information about object
ESC_MAGIC = '%' # Call magic function
ESC_MAGIC2 = '%%' # Call cell-magic function
ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
ESC_QUOTE2 = ';' # Quote all args as a single string, call
ESC_PAREN = '/' # Call first argument with rest of line as arguments
ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
エスケープ文字の設定っぽい記述が。
このファイル、アタリじゃね?と思いつつ、読み進めていくと見つけました!
def _tr_system(line_info):
"Translate lines escaped with: !"
cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
ここで !
エスケープ時の処理をしているんだな。と思い、 cmd の行を試しにコメントアウト。
def _tr_system(line_info):
"Translate lines escaped with: !"
# cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
jupyter notebook(lab)を起動すると、コマンド実行されなくなっていました!

【2018/9/27追記】 _process_posix.pyをいじる
ライブラリ直すなら、このような方法もあるようです。
https://github.com/jupyter/notebook/issues/2242#issuecomment-283369629
$ cd ~/.pyenv/versions/anaconda3-5.1.0/lib/python3.6/site-packages/IPython/utils
$ vim _process_posix.py
def sh(self):
if self._sh is None:
# self._sh = pexpect.which('sh') ← コメントアウト
self._sh = None ← 追加
if self._sh is None:
エラー吐いてくれるし、やるならこっちのほうが良さそう。
(何でこういうのって後から落ち着いて検索すると見つかるんだろう)
所感
まとめると短いけれど、調べるのに意外と時間がかかりました。。。疲れた
一応シェルコマンド使えなく出来たものの、やっぱりライブラリいじりたくないなぁ…
という気持ちが残りました。
また、事故が怖いなら docker 上に jupyter 立てて必要な資材だけマウントして使うとか、
そういう方が良いんじゃないかな?という気がしました。
他の会社さんだとこういう場合、どうしているんでしょう?
よろしければ参考までに教えて頂けたら幸いですm(_ _)m
(そもそも脅威に感じてる人いないよって話だったら羨ましい限り)