《自拍教程52》Python adb運行Shell腳本

Android作爲一款Linux終端,肯定是支持.sh後綴的Shell腳本的運行的,
有時候測試環境準備或者長時間截取複雜的日誌等,開發會給到一些Shell腳本。
Shell腳本的執行的優勢:

  1. 快捷高效,Shell腳本是Linux終端都支持的。
  2. 由於執行及測試結果都在Linux終端內部存儲,不會出現因爲反覆通過USB與Windows電腦進行輸入輸出導致的Android系統的I/O CPU消耗過大。

如何通過Python來運行Shell腳本呢?
何爲高端地用Python運行Shell腳本,這裏邊的學問可不小,
以下案例,我們寫了一個top.sh腳本, 用於持續截取系統各App的cpu佔用率情況。
持續截取cpu佔用率數據,也是App性能測試(資源消耗)的常用測試方法。


Shell腳本的三種運行方式
具體命令 具體效果
adb shell sh top.sh (低端) 執行在前臺,即“阻塞”在前臺運行,會讓你在這個界面等着。
拔掉USB線後,Shell腳本自動停止執行
adb shell sh top.sh &(中端) 執行在後臺,即“不阻塞”,在後臺執行了,不需要你等着執行完。
拔掉USB線後,Shell腳本自動停止執行
adb shell nohup sh top.sh &(高端) 獨立執行在後臺,執行後,即使你拔掉USB線,Shell腳本依舊後臺運行。

本案例用第三種高端方式來實現



Shell腳本與Python文件相互存在的兩種方式
融合方式 具體效果
顯露式(低端) top.sh腳本和Python腳本都是獨立的文件,top.sh顯露在外
—run_top_sh.py
—top.sh
隱藏式(高端) 將top.sh以文本的形式隱藏在Python代碼中
—run_top_sh.py

顯露式的方法,肯定是adb push top.sh /sdcard/top.sh,
本案例腳本用第二種隱藏式高端方式來實現, 具體如何實現呢?



準備階段

第1步: 將Shell腳本以字符串變量的形式存放於Python代碼塊裏
第2步: 將Shell腳本寫入一個臨時文件(注意Shell腳本是需要運行在Linux,Linux的行尾符是\n)
第3步: 將以上臨時文件adb push 到/sdcard/cpu.sh
第4步: 用adb shell nohup sh /sdcard/cpu.sh & 的方式實現長時間截取,即使USB不小心掉了,也不影響Shell腳本繼續在後執行截取top。

Python批處理腳本形式

記住批處理腳本的精髓:批量順序執行語句

# coding=utf-8

import os
import tempfile

# 將top.sh的Shell腳本copy過來,作爲字符串變量
top_sh = '''#!/bin/sh
    while true
    do
        top -n 1 >> /sdcard/top.log
        echo -e "$(date +%Y-%m-%d_%H:%M:%S)" >> /sdcard/top.log
        sleep 3
    done
    '''

print("正在生成Shell腳本的臨時文件......")
signal, temp_file = tempfile.mkstemp()  # 創建一個臨時文件
with open(temp_file, 'w+', newline="\n") as hf:  # 將支付按此轉成Shell腳本,重點注意換行符"\n"
    for line in top_sh:
        hf.write(line)

os.system("adb root") #必要的root
os.system("adb remount")
os.system("adb wait-for-device")
os.system("adb push %s /sdcard/top.sh" % temp_file)  # 推top.sh腳本到終端設備
os.system("adb shell chmod 777 /sdcard/top.sh")  # 賦值777
os.popen("adb shell nohup sh /sdcard/top.sh &")  # 獨立後臺無干擾執行,popen不阻塞
print("/sdcard/top.sh 腳本後臺無干擾運行中......")

print("清除臨時文件......")
os.close(signal)  # 臨時文件清理
os.remove(temp_file)  # 臨時文件清理

os.system("pause")

Python面向過程函數形式

面向過程函數的編程思維應該是這樣的:
你需要多少個功能(函數),才能做成這個事。
最好把功能(函數)都儘量封裝好,只暴露一定的參數接口即可。

import os
import tempfile

# 將top.sh的Shell腳本copy過來,作爲字符串變量
top_sh = '''#!/bin/sh
    while true
    do
        top -n 1 >> /sdcard/top.log
        echo -e "$(date +%Y-%m-%d_%H:%M:%S)" >> /sdcard/top.log
        sleep 3
    done
    '''


def generate_shell(shell_script):
    print("正在生成Shell腳本的臨時文件......")
    signal, temp_file = tempfile.mkstemp()  # 創建一個臨時文件
    with open(temp_file, 'w+', newline="\n") as hf:  # 將支付按此轉成Shell腳本,重點注意換行符"\n"
        for line in shell_script:
            hf.write(line)
    return signal, temp_file


def clear_tempfile(signal, temp_file):
    print("清除臨時文件......")
    os.close(signal)
    os.remove(temp_file)


def push_run_shell(sh_file, push_path):
    os.system("adb root") #必要的root
    os.system("adb remount")
    os.system("adb wait-for-device")
    os.system("adb push %s %s" % (sh_file, push_path))  # 推top.sh腳本到終端設備
    os.system("adb shell chmod 777 %s" % push_path)  # 賦值777
    os.popen("adb shell nohup sh %s &" % push_path)  # 獨立後臺無干擾執行,popen不阻塞
    print("%s 腳本後臺無干擾運行中......"%push_path)


signal, temp_file = generate_shell(top_sh)
push_run_shell(temp_file, "/sdcard/top.sh")
clear_tempfile(signal, temp_file)

os.system("pause")

Python面向對象類形式

面向對象類的編程思維應該是這樣的:
如果給你一個空白的世界,在這個世界裏你需要哪些種類的事物,
這些種類的事物都具備哪些共有的屬性與方法,
這些種類(類)的事物(對象),和其他種類(其他類)的事物(其他對象)有什麼關係。
儘量把這些類封裝好,只暴露對外的屬性(變量)和方法(函數)即可。

# coding=utf-8

import os
import tempfile

# 將top.sh的Shell腳本copy過來,作爲字符串變量
top_sh = '''#!/bin/sh
    while true
    do
        top -n 1 >> /sdcard/top.log
        echo -e "$(date +%Y-%m-%d_%H:%M:%S)" >> /sdcard/top.log
        sleep 3
    done
    '''


class ShellGeneratorAndRuner():
    '''Generate shell and push into android and run it'''

    def __init__(self, shell_script, push_path):
        self.script_text = shell_script
        self.push_path = push_path
        self.signal = None
        self.temp_file = None

    def generate_shell(self):
        print("正在生產Shell腳本的臨時文件......")
        self.signal, self.temp_file = tempfile.mkstemp()
        with open(self.temp_file, 'w+', newline="\n") as hf:  # 將支付按此轉成Shell腳本,重點注意換行符"\n"
            for line in self.temp_file:
                hf.write(line)

    def push_run_shell(self):
        os.system("adb root")   #必要的root
        os.system("adb remount")
        os.system("adb wait-for-device")
        os.system("adb push %s %s" % (self.temp_file, self.push_path))  # 推top.sh腳本到終端設備
        os.system("adb shell chmod 777 %s" % self.push_path)  # 賦值777
        os.popen("adb shell nohup sh %s &" % self.push_path)  # 獨立後臺無干擾執行,popen不阻塞
        print("%s 腳本後臺無干擾運行中......"%self.push_path)


    def clear_tempfile(self):
        os.close(self.signal)
        os.remove(self.temp_file)


if __name__ == '__main__':
    s_obj = ShellGeneratorAndRuner(top_sh, "/sdcard/top.sh")
    s_obj.generate_shell()
    s_obj.push_run_shell()
    s_obj.clear_tempfile()

    os.system("pause")

運行方式與效果

確保Android設備通過USB線與電腦連接了,adb設備有效連接,
以上代碼的3種實現形式都可以直接運行,比如保存爲run_top_sh.py並放在桌面,
建議python run_top_sh.py運行,當然也可以雙擊運行。
運行效果如下:
其中C:\Users\ADMINI~1\AppData\Local\Temp\tmp5way7qgx這個就是生成的臨時文件,
由於一般用戶不會涉及到以上臨時文件,所以可以實現“無感”地生成Shell腳本。


爲什麼鼓勵用隱藏式來隱藏Shell腳本到代碼裏去,
因爲比如後續你寫了一個Python工具,這個工具用py2exe編譯時,
py2exe只能編譯打包.py的文件成.exe, 其他的任何非.py的文件無法打包進來,
如果你發佈給別人用的時候,也就一個.exe,
大家就覺得你的工具做的比較好,集成的比較好。
相反地,如果你的.exe工具再帶一堆的Shell腳本,或者其他資源文件,配置文件等,
則相對而言沒那麼易用,比如容易動不動出現配套文件找不到,
或者被用戶隨意篡改導致程序無法正常運行,也無法顯示你的作品的牛逼。。。


不僅僅是Shell腳本,任何文本形式的文件(配置文件,腳本文件,其他log文件等等),
都可以考慮用以上這種方法附帶。。。


更多更好的原創文章,請訪問官方網站:www.zipython.com
自拍教程(自動化測試Python教程,武散人編著)
原文鏈接:https://www.zipython.com/#/detail?id=97cbb5fd8dcf4c40a549e565ca0b6f6a
也可關注“武散人”微信訂閱號,隨時接受文章推送。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章