日々適当

hibitekitou

ターミナルのコマンドをPythonから投げる

mac |2020-05-17

Pythonから ls -le ってコマンドを投げて内容を取得したいとします。
subprocessモジュールは「新しいプロセスの開始、入力/出力/エラーパイプの接続、リターンコードの取得を可能とします。」subprocess --- サブプロセス管理 — Python 3.8.3 ドキュメントだそうです。ls -le ってプロセスを開始したいって用途ですからこれを使えってことっすね。

使い方としてはシンプルには

import subprocess
cp = subprocess.run( ["ls", "-le"] )

で良いようなのですが、そこに様々なオプションが加わります。cpって変数で受けていますけど、これをprint文で表示してみると
CompletedProcess(args=['ls', '-le'], returncode=0)
とか記されました。つまり、欲しい情報が取得できていません。そのためには run メソッドに stdout 引数を用意し、subprocess.PIPE を渡すのだそうです。

cp = subprocess.run( ["ls", "-le"] , stdout=subprocess.PIPE)
print( cp )

print文の結果は
CompletedProcess(args=['ls', '-le'], returncode=0, stdout=b'total 0\ndrwxrwxrwt 34 root wheel 1088 4 16 14:45 Shared\ndrwxr-xr-x+ 69 user staff 2208 5 17 09:57 user\n 0: group:everyone deny delete\n')
って感じで必要情報が出てきます。ということでcp.stdout で必要な値を取得できます。注意すべきは、ネットを検索すると、runメソッドに encoding='utf-8'を入れないと文字列ではなくバイト列になるってことみたいで(上記紫部分直前にbって文字が見ます)、確かにそれを入れずに、例えば split('\n')なんて書くと "TypeError: a bytes-like object is required, not 'str'" って出てきちゃいますね。

cp = subprocess.run( ["ls", "-le"] , encoding='utf-8', stdout=subprocess.PIPE)

コマンドとそのオプションはスペース部分で分解してリストで渡しています。runメソッドには shell=True って引数もありますが、これを入れると"ls -le"ってstrで渡してもいいようです。shell=Trueを指定しないと、'ls -le'っていうコマンドと認識するみたいです(-leがオプションではなくコマンド名の一部として認識される?)。

runメソッドはサブプロセス(この場合は ls )が終了するまで次の処理を待ちます。そうじゃない処理をさせたい場合にはrunではなくPopenを使うそうです。

Popenでサブプロセス処理を開始して、communicate() で終了したサブプロセスの結果を受け取れるそうです。

import subprocess
import datetime

def printCurTime( a ):
	dt_now = datetime.datetime.now()
	print(a , dt_now.strftime('%H:%M:%S.%f'))

printCurTime( '開始' ) #時刻表示
cp = subprocess.Popen( 'sleep 5; ls -le' , shell=True, encoding='utf-8',stdout=subprocess.PIPE)
printCurTime( '途中' ) #時刻表示
result = cp.communicate()
print( result[0] )
printCurTime('終了') #時刻表示

こちらのように、Popenで「5秒スリープした後に ls -le する」ってサブプロセスを走らせた場合、結果は

開始 11:27:44.530698
途中 11:27:44.532001
total 0
drwxrwxrwt  34 root  wheel  1088  4 16 14:45 Shared
drwxr-xr-x+ 69 user   staff  2208  5 17 09:57 user
 0: group:everyone deny delete

終了 11:27:49.547068

という感じで、5秒のスリープ終わる前に「途中」って表示されて、5秒後に結果が表示されてます。
同じことをrunでやります。ただし値の取得は communicate() と print( result[0] ) の代わりに print( cp.stdout ) にしています。

開始 11:28:45.428847
途中 11:28:50.439436
total 0
drwxrwxrwt  34 root  wheel  1088  4 16 14:45 Shared
drwxr-xr-x+ 69 user   staff  2208  5 17 09:57 user
 0: group:everyone deny delete

終了 11:28:50.439503

「開始」と「途中」の間、5秒あいてます。ネットを漁ると、runは内部的にはPopenを呼び出した直後にcommunicate()を実行しているのだとか。なるほどなるほど。

sudoしたい場合はどうしたもんでしょうか。例えば

import subprocess
import time

while True:
	cp = subprocess.run( ["sudo","lsof", "-i:445"] , encoding='utf-8',stdout=subprocess.PIPE)
	print( cp.stdout )
	time.sleep( 60 )

ってのをターミナル上で実行すると、実行直後パスワードを聞かれます。なのでそれで問題ないと思うのですけど、実行させてすぐにパスワードが必要とは限らないって時どうするか(あるいは処理中 sudo で入力されたパスワードの有効期限が切れるかもしれない?)。Python で sudo を呼び出しパスワードを自動入力 [Qiita] によれば

import subprocess
import time
import getpass

passwd = ( getpass.getpass( prompt='パスワード♡ :' ) + '\n' )
while True:
	cp = subprocess.run( ["sudo","lsof", "-i:445"] , input=passwd, encoding='utf-8',stdout=subprocess.PIPE)
	print( cp.stdout )
	time.sleep( 60 )

こんな書き方で良さそうです。参考にした記事はPython 3.5みたいで、getpassのところで.encode() しているけど、ここで試しているのは3.7.6でしてちょっと事情が違うのかな?

まぁまぁ、本当にホントに基本のところはそんな感じなのでしょう。

コメント ( 0 )|Trackback ( )
 
コメント
 
コメントはありません。
コメントを投稿する
ブログ作成者から承認されるまでコメントは反映されません
 
名前
タイトル
URL
コメント
コメント利用規約に同意の上コメント投稿を行ってください。

数字4桁を入力し、投稿ボタンを押してください。