Jupyter Notebookでシステムコマンドを実行し文字列のリストとして取得
IPythonでは!lsのように先頭に!をつけることでシステムコマンド(OSコマンド / シェルコマンド)を実行できる。
IPythonをバックエンドで使っているJupyter Notebookでも同様の操作が可能。
Jupyter Notebookのセル上でシステムコマンドを実行できるだけでなく、その標準出力を文字列のリストとしてPythonの変数に格納して使うことができる。
ここでは以下の内容について説明する。
- 注意点
- Jupyter NotebookではなくIPythonの仕組み
- 使えるコマンドは環境に依存
- マジックコマンドとの関係
!をつけてシステムコマンドを実行!cdコマンドの注意点
- 標準出力をPythonのオブジェクト(文字列のリスト)として取得
!lsコマンドの注意点
便宜上、サンプルコードはPythonのコードとして掲載しているが、そのままpythonコマンドで実行しても動かないので注意。なお、サンプルコードはMacで実行している。
ipynbファイルは以下を参照。
注意点
Jupyter NotebookではなくIPythonの仕組み
冒頭で述べたように、!をつけてシステムコマンドを実行するのはIPythonの仕組み。
内部ではPythonの標準ライブラリsubprocessモジュールのgetoutput()関数を使っている(Python2系ではcommandsモジュール)。
Jupyter Notebookには他のプログラミング言語をkernelとして追加できるが、そのkernelが対応していない限り、!によるシステムコマンドの実行は使えない。
使えるコマンドは環境に依存
あくまでもシステムコマンド(OSコマンド / シェルコマンド)をIPythonの中で実行する仕組みなので、例えば、macOSのターミナルとWindowsのコマンドプロンプトで使えるコマンドが異なる。
ファイルの一覧を表示するコマンドは、macOSを含むUNIX系ではlsだが、Windowsのコマンドプロンプトではdirとなる。ただし、マジックコマンド%lsが使える場合もある(後述)。
本記事のサンプルコードはmacOSのターミナルでJupyter Notebookを起動させた場合の例。
マジックコマンドとの関係
IPythonでは%から始まるマジックコマンドが使える。
使用可能なマジックコマンドはバージョンや環境によって異なるが、%cdや%lsなど、システムコマンドと同名のマジックコマンドが定義されている場合がある。
基本的には同じような動作をするが、完全に同じであるとは限らない。後述のように!をつけたシステムコマンドは文字列のリストとして取得できるのに対し、マジックコマンドではそのようなことできない、といった違いもある。
なお、%automagicがTrueの場合(Onになっている場合)、%なしでマジックコマンドが実行できる。
このとき、例えばlsで実行されるのはマジックコマンドとしての%lsであり、システムコマンドのlsではないので注意。
Unix系OSでシステムコマンドとしてのlsを実行したい場合は明示的に!lsとする。
Windowsで%automagicがTrueの場合、マジックコマンド%lsが定義されている環境であれば、lsでdirコマンドと同様の結果が出力されるが、これはあくまでもそのように実装されたマジックコマンド%lsが実行されているだけ。Windowsのシステムコマンドにはlsはないので、!lsとしても結果は出力されない。
%automagicの設定は%automagic Trueや%automagic Falseで変更できるほか、%automagicでOn / Offがトグルする。環境によってはデフォルトでOnになっている。
!をつけてシステムコマンドを実行
いくつかのコマンドを実行する例を示す。
!ls array*でファイル名がarrayから始まるファイルの一覧を取得しているように、引数やオプションを指定することも可能。
!date
# 2018年 10月30日 火曜日 22時26分11秒 JST
!echo Hello
# Hello
!ls array*
# array_example.ipynb array_example.py
!ls -l array*
# -rw-r--r-- 1 mbp staff 1995 4 15 2018 array_example.ipynb
# -rw-r--r-- 1 mbp staff 358 4 15 2018 array_example.py
ファイルやディレクトリを処理するコマンドも実行できる。ディレクトリを作成、削除する例。
!mkdir test_dir
!rmdir test_dir
Pythonのコードでファイルやディレクトリを処理することももちろん可能だが、あとでPythonコードとして再利用する必要がなければシステムコマンドで処理してしまうのも便利。
ただし、subprocessモジュールのgetoutput()関数で実行するという仕様上、次に説明する!cdコマンドのように、シェルの状態・設定を変更するようなコマンドは反映されないので注意。
!cdコマンドの注意点
カレントディレクトリは標準ライブラリosモジュールのgetcwd()で取得できる。
システムコマンドpwd(Unix系OSのみ)やマジックコマンド%pwdでも取得可能。
import os
print(os.getcwd())
# /Users/mbp/Documents/my-project/python-snippets/notebook
!pwd
# /Users/mbp/Documents/my-project/python-snippets/notebook
%pwd
# '/Users/mbp/Documents/my-project/python-snippets/notebook'
カレントディレクトリを変更(移動)するシステムコマンドはcdだが、これを!cdとして実行しても反映されない。
!cd data
print(os.getcwd())
# /Users/mbp/Documents/my-project/python-snippets/notebook
マジックコマンド%cdはOK。
%cd data
# /Users/mbp/Documents/my-project/python-snippets/notebook/data
print(os.getcwd())
# /Users/mbp/Documents/my-project/python-snippets/notebook/data
!pwd
# /Users/mbp/Documents/my-project/python-snippets/notebook/data
%pwd
# '/Users/mbp/Documents/my-project/python-snippets/notebook/data'
上述のように、%automagicがTrueであれば%を省略してマジックコマンドを実行できる。
cd ..
# /Users/mbp/Documents/my-project/python-snippets/notebook
print(os.getcwd())
# /Users/mbp/Documents/my-project/python-snippets/notebook
もちろんos.chdir()でもよい。
os.chdir('data')
print(os.getcwd())
# /Users/mbp/Documents/my-project/python-snippets/notebook/data
公式ドキュメントの%cdの注釈にもあるように、!をつけたシステムコマンドが実行されたシェルは即座に廃棄されるため、!cdに限らずシェルの状態・設定を変更するようなコマンドを!で実行しても反映されないので要注意。
Note that !cd doesn’t work for this purpose because the shell where !command runs is immediately discarded after executing ‘command’. Built-in magic commands - %cd — IPython 7.10.1 documentation
その時点での情報を取得する!pwdやファイル・ディレクトリを変更する!mkdirなどのコマンドは問題ない。
標準出力をPythonのオブジェクト(文字列のリスト)として取得
!で実行したシステムコマンドの標準出力を文字列のリストとして取得し、変数に格納できる。
output = !date
print(output)
# ['2018年 10月30日 火曜日 22時26分12秒 JST']
取得できるのはIPython.utils.text.SListというIPython独自の型。Pythonの組み込み型listのサブクラスとして定義されている。
print(type(output))
# <class 'IPython.utils.text.SList'>
print(isinstance(output, list))
# True
list型のサブクラスなので、通常のリストのようにlen()で要素数を取得したり、インデックス[n]で要素を取得したりできる。要素は文字列str型。
print(len(output))
# 1
print(output[0])
# 2018年 10月30日 火曜日 22時26分12秒 JST
print(type(output[0]))
# <class 'str'>
標準出力は改行で区切られてリスト化される。
output = !ls -l array*
print(output)
# ['-rw-r--r-- 1 mbp staff 1995 4 15 2018 array_example.ipynb', '-rw-r--r-- 1 mbp staff 358 4 15 2018 array_example.py']
print(len(output))
# 2
print(output[0])
# -rw-r--r-- 1 mbp staff 1995 4 15 2018 array_example.ipynb
print(output[1])
# -rw-r--r-- 1 mbp staff 358 4 15 2018 array_example.py
IPython.utils.text.SList独自の属性nで、改行された文字列を取得できる。
print(output.n)
# -rw-r--r-- 1 mbp staff 1995 4 15 2018 array_example.ipynb
# -rw-r--r-- 1 mbp staff 358 4 15 2018 array_example.py
print(type(output.n))
# <class 'str'>
もちろん、通常のリストのようにjoin()メソッドを使って連結することも可能。
print('\n'.join(output))
# -rw-r--r-- 1 mbp staff 1995 4 15 2018 array_example.ipynb
# -rw-r--r-- 1 mbp staff 358 4 15 2018 array_example.py
!lsコマンドの注意
改行で区切られてリスト化されると書いたが、lsコマンドは1行で出力されているのに各ファイルが要素としてリスト化される。なお、繰り返しになるがlsコマンドはUnix系OSのシステムコマンドでWindowsにはない。サンプルコードはMacで実行している。
!ls array*
# array_example.ipynb array_example.py
output = !ls array*
print(output)
# ['array_example.ipynb', 'array_example.py']
print(len(output))
# 2
これは、lsのデフォルト(オプション無し)の出力が出力先によって異なるため。端末の場合は1行に複数件が出力され、端末ではないパイプやリダイレクトの場合は1行1件で出力される。
Jupyter Notebookのセル上で実行して出力を表示する場合は端末扱いなので1行に複数件が出力されるが、パイプでつなぐと1行1件で出力される。以下の通り。
!ls array*
# array_example.ipynb array_example.py
!ls array* | head
# array_example.ipynb
# array_example.py
出力をPythonのオブジェクトとして取得する場合は出力先が端末ではないので1行1件となり、改行で区切られて行ごとにリスト化される。
明示的に-Cオプションを指定すれば、1行に複数件の出力のまま行ごとにリスト化される。
!ls -C array*
# array_example.ipynb array_example.py
output = !ls -C array*
print(output)
# ['array_example.ipynb\tarray_example.py']
print(len(output))
# 1
print(output[0])
# array_example.ipynb array_example.py