コマンドプロンプトと同じ文字列でsubprocessを実行する(圧縮ファイルのテスト)

## 圧縮ファイルのテスト
圧縮ファイルをテストするときは、ファイルをサルベージしたときやHDDが壊れた、壊れそうなときなどでしょう。大抵そういうときはファイルが沢山有ります。

## そういうときは
Pythonしか無いです。って事も無いかもしれませんが、少なくとも私はそうです。

## でもコマンドプロンプト風に使いたい
ここからが本題です。外部コマンドを使用するときは、コマンドによって使い方が違います。この手のコマンドをそのまま文字列として渡せればきっと便利だろうと思って作りました。

```Python:test.py
import subprocess
import shutil


def command_check(chk: str) -> bool:
    return False if shutil.which(chk) is None else True


def commands_check(*args) -> bool:
    r = True
    for i in args:
        r = r and command_check(i)
    return r


def make_command(cl: str) -> list:
    r = []
    s = ''
    dqf = False
    for i in cl:
        if i == '"':
            dqf = not dqf
        elif i == ' ' and not dqf:
            r += [s]
            s = ''
        else:
            s += i
    r += [s]
    return r


def command_call(command, *args, disp=True, capture=False, **kwargs):
    command = command if type(command) is list else make_command(command)
    cl = []
    for i in command:
        f = False
        for k,v in kwargs.items():
            if i in '{'+k+'}':
                f = True
        cl += [i.format(**kwargs) if f else i]
    ag = 0
    for i in range(len(cl)):
        if cl[i] == '[]':
            cl[i] = args[ag]
            ag += 1
    if disp:
        for i in cl:
            print('"'+ i + '"' if ' ' in i else i, end=' ')
        print()
    return subprocess.run(cl, capture_output=capture, shell=True)


def main():
    import glob,os,os.path,sys
    cs = {
        '.rar':['rar.exe','rar t []',2],
        '.zip':['7za.exe','7za t []',10],
        }

    fld = 'f:\\temp'

    if not commands_check(*[i[0] for i in cs.values()]):
        print('コマンドが足りないです')
        sys.exit()

    fl = glob.glob(os.path.join(fld, '**/*.*'), recursive=True)
    for i in fl:
        ext = os.path.splitext(i)[1].lower()
        if ext not in cs.keys():
            continue
        r = command_call(cs[ext][1], i, disp=True, capture=True).returncode
        if r == cs[ext][2]:
            print(i,' is broken')
            os.rename(i,i+'.broken')
        elif r != 0:
            print(i,' is unknown')
            os.rename(i, i+'.unknown')

if __name__ == '__main__':
    main()
```

## 使い方(全体)
`main`関数の下のcsに `'.拡張子':['実行ファイル','コマンドライン',エラーコード],`
を追加します。
ソースコード中では、7za.exe(7zip)とrar.exe(winrar)を使います。
コマンドライン中の`[]`は任意のファイル名に置き換えられる部分です。
fld にチェックしたいディレクトリを記述して実行してください。
エラーコードと同じものがあればそのファイルに拡張子.brokenが追加されます。

## 使い方(関数)
##### `command_check(chk: str) -> bool`
strに実行したい外部コマンドを入れてこの関数を実行すると、そのコマンドが実行できればTrueが帰ってきます
例) `command_check('rar')`

##### `def commands_check(*args) -> bool`
command_checkの複数版です。一つでも無いものがある場合はFalseを返します。
例) `commands_check('rar','7za','ffmpeg')


##### `def make_command(cl: str) -> list`
一行に書かれたコマンドの書式をsubprocessで使用するリスト形式に直します。スペースで分割しますが、"で囲まれた部分は分割しません。コマンドプロンプトと同じ仕様です。

例) `make_command('"C:\\Program Files\\hoge\\hoge.exe" -h aaa.dat')`
##### `def command_call(command, *args, disp=True, capture=False, **kwargs)`
commandに書かれた外部コマンドを実行します。
command内では、`[]`と`{任意の文字}`が有るとそれぞれ*args , **kwargsに置換します。

`command_call('xcopy [] []','C:\\','D:\\')`の場合
xcopy c:\ d:\のようになります。置換は出現順です。

`command_call('xcopy {src} {dst}',src='C:\\',dst='E:\\')`
のような記述も可能です。


disp Trueなら実行するコマンドを表示します。
capture Trueだと標準出力と標準エラー出力をキャプチャします、また、画面出力を行いません。

返値はsubprocess.runと同じです。
r = command_call......
r.returncode プロセスの返値
r.stdout (captureがTrueなら)標準出力
r.stderr (captureがTrueなら)標準エラー出力