よちよちpython

独習 python/Qpython/Pydroid3/termux/Linux

subprocessを使ってみる

コマンドをPythonコードで実行するには、どうすればいい?



インストール不要な標準モジュールのsubprocessを使うと出来そうです。
os.systemなどに変わってsubprocessモジュールの使用を推奨されているようですので、それを使います。

参照
Python公式 subprocessモジュール

目次


作業環境


Androidスマホ

・QPyNotebook
・Pydroid3でJupyterNotebook
・termuxでJupyterNotebook

実験


subprocessのメソッドには

  • run
  • call
  • check_call
  • check_output
  • Popen

と、幾つか種類があるようです。詳しいことは公式ページに任せるとして、とりあえず動かしてみます。

コマンドpwdでカレントディレクトリの取得


os.getcwd()を使えば取得できるが、subprocessモジュールの subprocess.runメソッドを使ってみる。

import subprocess

subprocess.run("pwd")
CompletedProcess(args='pwd', returncode=0)

実行すればサクッとカレントディレクトリが表示されると思いきや、想像と別な表示がされた。
コマンドが正常に実行され終了すればreturncode=0を返す。ですと。



subprocess.callではどうだ。

subprocess.call("pwd")
0

これも正常に実行と終了をした証拠だけを残した。



subprocess.check_callはどうでしょ。

subprocess.check_call("pwd")
0

またしても。



subprocess.check_outputでは。

subprocess.check_output("pwd")
b'/storage/emulated/0/qpython/notebooks\n'

カレントディレクトリが表示された。
しかしバイナリ形式のbになっている。
decode()関数を使うとデコードできる。

res = subprocess.check_output("pwd")

res.decode("utf-8")
'/storage/emulated/0/qpython/notebooks\n'

変換された。
subprocess.Popenは飛ばす。

別のOSのコマンドを指定してみる


現在これをAndroidスマホで実行しているが、試しにWindowsコマンドのdirを使うとどうなるか。
AndroidWindowsのコマンドがないので実行できないとかエラーコードを吐くだろうけど、一応確認。

subprocess.check_output("dir")
PermissionErrorTraceback (most recent call last)

<ipython-input-7-aa07fb335683> in <module>()
----> 1 subprocess.check_output("dir")


~/lib/python36.zip/subprocess.py in check_output(timeout, *popenargs, **kwargs)
    334 
    335     return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
--> 336                **kwargs).stdout
    337 
    338 


~/lib/python36.zip/subprocess.py in run(input, timeout, check, *popenargs, **kwargs)
    401         kwargs['stdin'] = PIPE
    402 
--> 403     with Popen(*popenargs, **kwargs) as process:
    404         try:
    405             stdout, stderr = process.communicate(input, timeout=timeout)


~/lib/python36.zip/subprocess.py in __init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, encoding, errors)
    707                                 c2pread, c2pwrite,
    708                                 errread, errwrite,
--> 709                                 restore_signals, start_new_session)
    710         except:
    711             # Cleanup if the child failed starting.


~/lib/python36.zip/subprocess.py in _execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, start_new_session)
   1342                         if errno_num == errno.ENOENT:
   1343                             err_msg += ': ' + repr(err_filename)
-> 1344                     raise child_exception_type(errno_num, err_msg, err_filename)
   1345                 raise child_exception_type(err_msg)
   1346 


PermissionError: [Errno 13] Permission denied: 'dir'

パーミッション・エラー。

逆にWindows上でLinuxコマンドなどをsubprocessで実行させようとしても同様のエラーが出るかな。

また、コマンドによってもシェルコマンドか、ファイルをダウンロードして使っている外部コマンドかで実行できなかったりするようです。

引数のあるコマンドの実行


コマンド コマンド引数のように、引数を使う場合も同様にできるのか。



フォルダを新規作成してみる。

subprocess.run("mkdir Test_Dir")
PermissionErrorTraceback (most recent call last)

<ipython-input-31-80de53d19318> in <module>()
----> 1 subprocess.run("mkdir Test_Dir")


~/lib/python36.zip/subprocess.py in run(input, timeout, check, *popenargs, **kwargs)
    401         kwargs['stdin'] = PIPE
    402 
--> 403     with Popen(*popenargs, **kwargs) as process:
    404         try:
    405             stdout, stderr = process.communicate(input, timeout=timeout)


~/lib/python36.zip/subprocess.py in __init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, encoding, errors)
    707                                 c2pread, c2pwrite,
    708                                 errread, errwrite,
--> 709                                 restore_signals, start_new_session)
    710         except:
    711             # Cleanup if the child failed starting.


~/lib/python36.zip/subprocess.py in _execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, start_new_session)
   1342                         if errno_num == errno.ENOENT:
   1343                             err_msg += ': ' + repr(err_filename)
-> 1344                     raise child_exception_type(errno_num, err_msg, err_filename)
   1345                 raise child_exception_type(err_msg)
   1346 


PermissionError: [Errno 13] Permission denied: 'mkdir Test_Dir'

パーミッションエラー。
コマンドをそのまま書いたら駄目っぽい。
リストで与えてやれば行けるようだ。

cmd = ["mkdir","Test_Dir"]

subprocess.run(cmd)
CompletedProcess(args=['mkdir', 'Test_Dir'], returncode=0)

お、行けましたよ。
または

cmd = "mkdir フォルダ名"
subprocess.run(cmd.split())


と、コマンドはそのまま書いて、split()を使う。



作ったフォルダに移動してみる。

cmd2 = ["cd","Test_Dir"]
subprocess.run(cmd2)
PermissionErrorTraceback (most recent call last)

<ipython-input-37-352d175499da> in <module>()
      1 cmd2 = ["cd","Test_Dir"]
----> 2 subprocess.run(cmd2)


~/lib/python36.zip/subprocess.py in run(input, timeout, check, *popenargs, **kwargs)
    401         kwargs['stdin'] = PIPE
    402 
--> 403     with Popen(*popenargs, **kwargs) as process:
    404         try:
    405             stdout, stderr = process.communicate(input, timeout=timeout)


~/lib/python36.zip/subprocess.py in __init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, encoding, errors)
    707                                 c2pread, c2pwrite,
    708                                 errread, errwrite,
--> 709                                 restore_signals, start_new_session)
    710         except:
    711             # Cleanup if the child failed starting.


~/lib/python36.zip/subprocess.py in _execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, start_new_session)
   1342                         if errno_num == errno.ENOENT:
   1343                             err_msg += ': ' + repr(err_filename)
-> 1344                     raise child_exception_type(errno_num, err_msg, err_filename)
   1345                 raise child_exception_type(err_msg)
   1346 


PermissionError: [Errno 13] Permission denied: 'cd'

パーミッション・エラー。



teratail subprocessでcd移動できない


こちらによると、 shell=Trueと書けばできる、と。

cmd2 = ["cd","Test_Dir"]
subprocess.run(cmd2,shell=True)
CompletedProcess(args=['cd', 'Test_Dir'], returncode=0)

正常に実行終了した。
カレントディレクトリを見てみる。

subprocess.check_output("pwd")
b'/storage/emulated/0/qpython/notebooks\n'

移動していませんね。
上のQ&Aサイトの追記にあるように、元に戻ってしまう。



こちらはsubprocessではなく、os.chdirで移動した場合。
stackoverflow スクリプトを終了すると元のディレクトリに戻る件

難易度高め。バッチ/シェルスクリプトを書いたりして対応している。
よし、一旦この件は放置。

ipynb形式をmd形式に変換


JupyterNotebookがインストールされていれば、jupyter nbconvertのコマンドが使える筈なので、それをsubprocessで動かす。



上手く行けば、指定のipynbファイルがmdに変換され新規ファイルで保存される。



事前に用意したtest.ipynbなるファイルを変換する。
変換後のファイル名は自動的に変換前ファイル名.mdのように拡張子のみ変わる。

import subprocess

# mdファイルに変換したいipynbのファイル名
file = "test.ipynb"

# コマンドの引数
args = ["jupyter", "nbconvert","--to","markdown"]

# コマンド引数にipynbファイル名を追加
args.append(file)

# コマンドの実行
try:
    subprocess.run(args)
    print("変換しました。")
except:
    print("変換できませんでした。")
    
変換しました。

QPyNotebookで実行した。
「変換しました。」と言っているが、嘘。mdファイルは作られていなかった。



Pydroid3とtermuxからJupyter Notebookで実行したら変換できた。


以上。