Python執行命令的正確做法

在編寫 Python 程序的時候,很容易直接調用 system, subprocess.Popen, subprocess.run, subprocess.call, subprocess.check_call, subprocess.check_output 等方法執行命令。但是如果一個系統裏充滿了這樣的命令之後,整個系統變得難以分析和調試,在編程裏就是所謂的「可觀察性」很差。或者說,這樣的腳本系統不是「Hackable」的。

有什麼解法呢?有的,根據我多年手寫此類腳本系統的經驗,就是要把原子的命令執行統一記錄和 dump 下來,這樣在系統執行完的時候,只要分析這樣的命令序列文件就能快速定位問題。是不是和彙編很像?其實也和數據庫的SQL 語句日誌文件很像。

下面的代碼就可以做到:

import os
import subprocess
import json
from loguru import logger

g_all_commands = []

class Executor:

    @staticmethod
    def init():
        g_all_commands = []

    @staticmethod
    def finish(log_file):
        logger.info(f'\n')
        logger.info(f'<commands>: --------------------')
        logger.info(f'<commands>: all commands')
        logger.info(f'<commands>: --------------------')
        logger.info(f'<commands>: {log_file}')
        logger.info(f'<commands>: --------------------')
        logger.info(f'\n')
        with open(log_file,'w') as f:
            f.writelines([c+'\n' for c in g_all_commands])

    @staticmethod
    def run_phony(cmd):
        g_all_commands.append(cmd)
    
    @staticmethod
    def system(command):
        g_all_commands.append(command)
        return os.system(command)
    
    @staticmethod
    def run(*popenargs, input=None, capture_output=False, timeout=None, check=False, **kwargs):
        g_all_commands.append(Executor.__join(popenargs))
        return subprocess.run(*popenargs, input=input, capture_output=capture_output, timeout=timeout, check=check, **kwargs)
    
    @staticmethod
    def Popen(*args, **kwargs):
        g_all_commands.append(Executor.__join(args))
        return subprocess.Popen(*args, **kwargs)
    
    @staticmethod
    def check_output(*popenargs, timeout=None, **kwargs):
        g_all_commands.append(Executor.__join(popenargs))
        return subprocess.check_output(*popenargs, timeout, **kwargs)
    
    @staticmethod
    def call(*popenargs, timeout=None, **kwargs):
        g_all_commands.append(Executor.__join(popenargs))
        return subprocess.call(*popenargs, timeout, **kwargs)
    
    @staticmethod
    def check_call(*popenargs, **kwargs):
        g_all_commands.append(Executor.__join(popenargs))
        return subprocess.check_call(*popenargs, **kwargs)
    
    @staticmethod
    def __join(*popenargs):
        a = []
        for e in popenargs:
            if type(e)==type({}):
                a.append(json.dumps(e))
            elif type(e)==type([]) or type(e)==type(()):
                a.append(Executor.__join(*e))
            else:
                a.append(str(e))
        return ' '.join(a)

用法:

  1. 用來使用 system 的地方換成 Executor.system, 原來使用 subprocess.xxx 的地方換成 Executor.xxx,參數完全一樣。
  2. 可以通過 Executor.initExecutor.finish 來開始和結束,結束的時候把命令序列都記錄到一個日誌文件裏,用以分析。
  3. 還可以通過調用 Executor.run_phony("@some split") 方法來插入一些分隔行,便於問題診斷。phony 是 Makefile 裏 .PHONY 一樣的意思,就是“僞造的,假的”的意思。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章