ろうとるがPythonを扱う、、（その３：Subprocess）

# Subprocess＆tkinterを使ってコマンドプロンプトもどきプログラム
今後もPythonを扱うこともありそうなので、シリーズ化（その３）することにした。今回は、Subprocessおよびtkinter(GUIフレームワーク)を使って、コマンドプロンプトもどきプログラムをつくる。

## Subprocess
Subprocessでは、Windowsコマンドプロンプトのコマンドを実行できる。次のサイトを主に参照した。
- [【Python】Windowsコマンドプロンプトのコマンドを実行する方法（subprocessモジュール）](https://office54.net/python/basic/python-windows-command)
- [subprocess --- サブプロセス管理](https://docs.python.org/ja/3/library/subprocess.html)

## 作成したもの
![before.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/c0eb2447-a6d4-1de2-9598-3a6a5cfc1e70.png)
![after.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/8adb0b65-0996-b522-6dc6-96b94900cc6a.png)
ここでは、「dir *.txt」の実行結果を表示している。

## ソースコード
```python
# -*- coding: utf-8 -*-
import subprocess
import tkinter as tk

def showResult():
    cmd = cmdbox.get()
    output = subprocess.run(cmd.split(), shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout
    output = output.decode('shift_jis')
    output = output.replace('\r', '')
    result.insert(tk.END, '<<<    command    >>>     ' + cmd + '\n\n')
    result.insert(tk.END, output + '\n\n')
    result.see("end")
    cmdbox.delete(0, tk.END)

def func(event):
    showResult()

def func2():
    showResult()

root = tk.Tk()
root.geometry('600x600')
root.title('Like command prompt')

frm1 = tk.Frame()
frm1.place(x = 40, y = 10)
frm2 = tk.Frame()
frm2.place(x = 40, y = 50)

cmdbox = tk.Entry(frm1, width = 70)
cmdbox.pack(side=tk.LEFT)
cmdbox.bind('<Return>', func)
btn = tk.Button(frm1, text='OK', command=func2)
btn.pack(side=tk.LEFT)

result = tk.Text(frm2, font=("", 10), width=70, height=40)
ysc = tk.Scrollbar(frm2, orient=tk.VERTICAL, command=result.yview)
ysc.pack(side=tk.RIGHT, fill="y")
result["yscrollcommand"] = ysc.set
result.pack()

root.mainloop()
```
以下、ポイントを解説。

```python
frm1 = tk.Frame()
frm1.place(x = 40, y = 10)
frm2 = tk.Frame()
frm2.place(x = 40, y = 50)
```
コマンド入力用フレームと結果用フレームの作成。

```python
cmdbox = tk.Entry(frm1, width = 70)
cmdbox.pack(side=tk.LEFT)
cmdbox.bind('<Return>', func)
btn = tk.Button(frm1, text='OK', command=func2)
btn.pack(side=tk.LEFT)
```
コマンド入力用Entry widget、エンター（リターン）キーが押されたときの関数（func）、OKボタンおよびボタンが押されたときの関数（func2）の定義（ボタンは必須ではない、おまけ）。キーが押されたときの処理については下記のサイト参照した。
- [tkinterでEnterキーの入力を拾いたい](https://rait-09-kts.hatenablog.jp/entry/2021/07/04/212916)
- [9. tkinterのアプリにキー入力をバインドする](https://ossa2019.stars.ne.jp/Remember/tkinter/tk9.html)

```python
result = tk.Text(frm2, font=("", 10), width=70, height=40)
ysc = tk.Scrollbar(frm2, orient=tk.VERTICAL, command=result.yview)
ysc.pack(side=tk.RIGHT, fill="y")
result["yscrollcommand"] = ysc.set
result.pack()
```
結果用Text Widget、スクロールバーの作成。後者については、下記サイトを参照した。
- [【Python GUI】スクロールバーを作る方法【Tkinter】](https://engineer-jobchange.com/python-gui-tkinter-scrollbar)
- [【python】スクロールバーが自動で下がってほしい。](https://teratail.com/questions/199144)

```python
def func(event):
    showResult()

def func2():
    showResult()
```
キーが押されたときに呼び出される関数およびOKボタンが押されたときに呼び出される関数。前者は「event」という引数が必要なようだ。

```python
def showResult():
    cmd = cmdbox.get()
    output = subprocess.run(cmd.split(), shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout
    output = output.decode('shift_jis')
    output = output.replace('\r', '')
    result.insert(tk.END, '<<<    command    >>>     ' + cmd + '\n\n')
    result.insert(tk.END, output + '\n\n')
    result.see("end")
    cmdbox.delete(0, tk.END)
```
Entryに入力された文字列を取得し（get()）、リスト化（split()）してSubprocessにわたす。結果をシフトJIS化（decode()）、CR('\r')削除（replace()、環境によっては不要かも）、結果をTextに挿入（insert()）する。Textの現在位置を最後に移動し（see("end")）、Entry内容を削除する（delete()）。

## 終わりに
Text Widgetのみで実現（よりコマンドプロンプトに近い）できるか検討中、、。まがりなりにもなんとかなれば、記事にします。