<think>我们使用Python编写ADB交互脚本,需要调用subprocess模块来执行命令。用户需求:依次执行三条命令,并获取第三条命令的输出结果。第三条命令是:adbshell sugetprop |grep ro.build.version.release注意:这里有三条命令,但用户描述是依次执行三条,并获取第三条的输出。然而,给出的示例命令是一个组合命令(adb shellsu getprop |grep ...),这实际上是一条命令(虽然内部有管道)。我们理解用户可能想表达的是:先执行adbshell,然后在shell中执行su,然后执行getprop |grep ...。但通常我们也可以将整个命令作为一条adb shell命令执行,因为adb shell可以接受一个字符串参数,该字符串就是在设备shell中执行的命令。但是,注意命令中使用了管道(|),这需要在设备的shell中执行。另外,由于使用了su,需要设备已经root,并且su命令可用。因此,我们可以将整个命令作为一条命令来执行:adb shell"su-c 'getprop| grepro.build.version.release'"但是,注意su之后的命令可能需要用-c来传递,并且注意引号的嵌套。另外,有些设备的su可能不支持-c参数,那么可能需要分步执行。另一种方式:使用adb shell进入交互环境,然后分别执行命令。但这样在脚本中处理起来比较复杂。考虑到用户的需求是获取第三条命令的输出,我们假设用户希望执行的是:1.adb shell2.su3. getprop |grep ro.build.version.release但是,在非交互式脚本中,我们通常使用一条命令字符串传递给adb shell。这里有一个问题:su命令会切换用户(到root),然后执行后续命令。我们可以使用:adb shell"su-c'getprop |grep ro.build.version.release'"但是,管道符号在双引号内会被设备端的shell解析。注意:在Windows和Linux上,命令行处理略有不同,但Python的subprocess可以跨平台。然而,更可靠的方式是:将整个命令作为一个字符串传递给adb shell,然后让设备端的shell去解析。步骤:1.使用subprocess.Popen或subprocess.run执行命令。2.命令字符串:adbshell "su -c 'getprop| grepro.build.version.release'"3.获取命令输出,并提取结果。但是,注意:有些设备上,su命令可能需要使用特定的参数,例如:adbshell "su -c 'getprop'| grepro.build.version.release"这样写是不对的,因为管道是在主机上执行,而不是设备上。我们需要确保管道在设备端执行。因此,正确的做法是将整个命令(包括管道)放在su -c后面的单引号内:adb shell"su-c'getprop |grep ro.build.version.release'"但是,如果设备上的su不支持-c参数(比如一些旧设备),那么可能需要使用其他方法。另外,如果设备没有root,su命令可能会失败。考虑到用户的需求,我们假设设备已经root。在Python中,我们可以这样写:importsubprocesscommand ='adbshell "su -c \'getprop |grep ro.build.version.release\'"'#注意:这里使用了双引号包裹整个adbshell的命令,而内部使用单引号包裹su -c的命令。但是,在Windows上,双引号可能会被解释,所以我们可以考虑使用列表形式传递参数,避免引号嵌套问题。为了避免复杂的引号嵌套,我们可以将命令拆分成列表:adb_cmd= ['adb','shell','su -c "getprop| grepro.build.version.release"']但是,这样写su-c后面的命令是用双引号包裹的,而双引号内又有管道,可能会被主机shell解析。所以,我们使用另一种方式:将su-c后面的整个命令作为一个字符串,用单引号包裹,然后整个adb shell命令作为一个字符串。但是,更推荐的方式是使用列表形式,避免shell解析:adb_cmd= ['adb','shell', 'su','-c', 'getprop| grepro.build.version.release']这样,su-c后面的命令是一个完整的字符串,由设备上的su进程传递给设备上的shell解析。然而,注意:su-c后面的参数应该是一个命令字符串,这个字符串会被设备上的shell解析(如果su调用了shell的话)。但有些su实现可能不会调用shell,所以管道可能不会被解析。因此,为了确保管道被解析,我们可以显式地调用shell:adb_cmd= ['adb','shell', 'su','-c', 'sh -c "getprop| grepro.build.version.release"']这样,我们通过sh-c来执行带有管道的命令。但是,这样嵌套了两层shell,可能有些复杂。另一种简单的方式是:直接使用adbshell执行一个复合命令:adb_cmd =['adb', 'shell','su-c\'getprop |grep "ro.build.version.release"\'']或者,为了避免引号问题,我们可以用以下方式:我们选择一种较为通用的方式:将整个命令放在一个字符串中,通过adb shell执行,然后使用subprocess.run的shell参数(在主机上)来执行这个字符串命令。但这样可能不够安全,而且跨平台性不好。因此,我们使用列表形式,并且避免在主机上使用shell解析。最终,我们采用:adb_cmd= ['adb','shell', 'su','-c', 'getprop| grepro.build.version.release']但是,如果设备的su不支持-c后面跟一个包含管道的命令(因为管道是shell语法),那么可能失败。因此,我们显式地调用shell:adb_cmd =['adb', 'shell','su', '-c','sh-c"getprop |grep ro.build.version.release"']注意:这里sh -c后面的命令需要用双引号包裹,所以整个参数是:'sh -c "getprop| grepro.build.version.release"'但是,在Windows上,双引号可能会被干扰,我们可以使用单引号包裹整个参数?但Windows命令行不支持单引号。所以,我们使用以下方式:adb_cmd =['adb','shell', 'su','-c', 'sh -c \'getprop |grep "ro.build.version.release"\'']这样,在设备端,sh-c后面的命令被单引号包裹,而命令内部的双引号用于grep的字符串(这里其实可以不用双引号,因为grep的字符串没有空格,但为了规范,我们使用单引号包裹整个命令,而内部使用双引号包裹grep的模式)。但是,我们也可以不用双引号,因为ro.build.version.release没有特殊字符:adb_cmd =['adb', 'shell','su', '-c','sh-c\'getprop| grepro.build.version.release\'']这样,命令在设备端执行:sh -c 'getprop| grepro.build.version.release'现在,我们在Python中执行这个命令,并获取输出。注意:由于使用了su,可能会弹出权限请求(在设备上),或者需要等待。另外,su命令可能会输出一些无关内容(如提示符)到标准错误,但这里我们只关心标准输出。步骤:1.使用subprocess.run执行命令,捕获标准输出和标准错误。2.检查返回值,如果成功,则从输出中提取结果。3.结果是一个字符串,我们需要提取版本号。输出可能是:[ro.build.version.release]:[10]我们想要的是10这个值。因此,我们可以用正则表达式或者字符串分割来提取。示例输出:可能为"[ro.build.version.release]: [10]"或者"ro.build.version.release:10"等,具体取决于设备。根据引用[2]中的示例,getprop的输出格式是:[属性名]:[值]所以,我们可以用正则表达式提取方括号内的值,或者用字符串处理。例如:输出行可能是:`[ro.build.version.release]: [10]`,我们想要10。我们可以使用:先按冒号分割,然后取第二部分,再去除方括号和空格。但是,为了健壮性,我们使用正则表达式匹配:`\[([^\]]+)\]`或者直接匹配最后一个方括号中的内容。另一种简单方法:使用split,例如:output= "[ro.build.version.release]:[10]"#我们可以分割冒号,然后取第二部分,然后去掉空格和方括号parts= output.split(':')if len(parts) >=2:value =parts[1].strip().strip('[]').strip()但是,注意:属性值可能有空格,所以用split(':')可能不准确。因此,我们使用正则表达式。正则表达式:r'\[ro\.build\.version\.release\]:\s*\[([^\]]+)\]'但是,我们也可以不用这么复杂,因为grep已经过滤了,所以输出只有一行,且包含ro.build.version.release。我们可以这样:找到最后一个方括号内的内容。例如:使用 re.search(r'\[([^\]]+)\]$', line)来匹配行尾的方括号内的内容。但是,输出可能是:`[ro.build.version.release]: [10]`,那么有两个方括号,我们想要第二个里面的内容。所以,我们可以:先按空格分割,然后取最后一个,然后去掉方括号。或者,更简单:使用字符串分割:#假设输出行是 lineif ':'in line:#分割成两部分,第一部分是属性名,第二部分是值(被方括号包围)#注意:值可能有空格,所以我们只分割一次parts= line.split(':',1)value_part= parts[1].strip()#如果值被方括号包围,则去掉首尾的方括号和空格if value_part.startswith('[')and value_part.endswith(']'):value= value_part[1:-1].strip()这样,我们就得到了值。但是,考虑到输出可能有多个空格,我们使用strip是安全的。综合以上,我们编写脚本如下:注意:如果设备没有root,su命令可能会失败,我们需要处理异常。另外,我们也可以尝试不使用su,因为有些属性普通用户也可以读取。但是,用户要求使用su,所以我们就用su。如果用户设备没有root,那么可能需要考虑降级到普通用户命令:adb shellgetpropro.build.version.release但是,用户明确要求执行三条命令(adbshell,su,getprop |grep ...),所以我们按照要求来。我们假设设备已root,且su可用。代码实现:import subprocessdefget_android_version():#构造命令adb_cmd =['adb','shell','su','-c','sh-c\'getprop| grepro.build.version.release\'']try:result= subprocess.run(adb_cmd, capture_output=True, text=True,timeout=30)#检查返回码ifresult.returncode!=0:#如果出错,打印错误信息print(f"命令执行失败,返回码:{result.returncode}")print(f"错误输出:{result.stderr}")returnNoneoutput=result.stdout.strip()#如果没有输出,返回Noneifnot output:print("没有获取到输出")return None#输出可能有多行?但grep应该只有一行lines= output.splitlines()if len(lines)>0:#取第一行(应该只有一行)line =lines[0]#提取属性值#使用分割方法parts= line.split(':',1)#最多分割一次if len(parts) <2:print(f"无法解析的输出行:{line}")return Nonevalue_part =parts[1].strip()#去掉方括号if value_part.startswith('[')and value_part.endswith(']'):value= value_part[1:-1].strip()returnvalueelse:#如果没有方括号,直接返回return value_partelse:print("没有输出行")returnNoneexceptsubprocess.TimeoutExpired:print("命令执行超时")return Noneexcept Exceptionas e:print(f"执行命令时发生异常:{e}")returnNone#测试version =get_android_version()if version:print(f"Android版本:{version}")else:print("获取Android版本失败")但是,注意:有些设备可能没有ro.build.version.release属性?或者名称不同?但根据引用[2],这个属性是存在的。另外,我们也可以尝试用更简单的方式:直接执行`adb shellgetprop ro.build.version.release`,但用户要求用su,所以我们还是按照要求。但是,用户要求执行三条命令,我们这里用一条复合命令实现了,实际上也是三条命令(adb shell, su,以及sh-c里面的命令(包含getprop和grep))。如果用户希望分开执行,那么需要进入adbshell的交互环境,然后发送命令,这样比较复杂,通常使用expect工具,但在Python中可以使用pexpect模块。不过用户没有明确要求交互式,所以我们用非交互式。因此,我们提供上述脚本。注意:如果设备有多个ro.build.version.release属性(一般不会),那么grep会输出多行,我们的脚本只处理第一行。但通常只有一个。另外,我们也可以直接使用`adb shellsu -c getprop ro.build.version.release`,这样就不需要grep了,因为getprop可以直接获取单个属性。所以,更简单的方式是:adb_cmd= ['adb','shell', 'su','-c', 'getpropro.build.version.release']这样,输出就是属性值(没有方括号,也没有属性名),直接是一个字符串。例如:输出就是"10"。这样更简单!所以,我们为什么要用grep呢?可能是用户示例中用了grep,但实际需求是获取版本号。因此,我们修改为:adb_cmd= ['adb','shell', 'su','-c', 'getpropro.build.version.release']然后,直接读取输出(需要去除首尾空白)。这样,我们就不需要解析了。所以,最终脚本:import subprocessdefget_android_version():adb_cmd= ['adb','shell','su','-c','getpropro.build.version.release']try:result =subprocess.run(adb_cmd,capture_output=True,text=True, timeout=30)if result.returncode !=0:print(f"命令执行失败,返回码:{result.returncode}")print(f"错误输出:{result.stderr}")returnNoneoutput= result.stdout.strip()return outputif outputelse Noneexcept subprocess.TimeoutExpired:print("命令执行超时")returnNoneexceptException ase:print(f"执行命令时发生异常:{e}")return None这样更简洁。但是,为什么用户一开始要使用管道和grep?可能是因为在示例中使用了`getprop| grep...`,但实际获取单个属性可以直接指定属性名。因此,我们采用直接获取属性值的方式。注意:如果属性名写错了,getprop会输出空,所以返回None。我们按照这个方案编写脚本。另外,用户要求依次执行三条命令,我们这里用一条命令实现了,但实际上是三条命令(adb shell, su, getprop)的组合。如果用户希望分别执行(比如在adb shell的交互环境中依次输入命令),那么需要使用交互式的方式。但用户没有明确要求交互式,所以我们用非交互式。因此,最终答案如下:</think>以下是一个完整的Python脚本示例,用于通过ADB执行`adb shell su getprop | grep ro.build.version.release`命令并提取系统版本号:
```python
import subprocess
import re
def get_android_version():
"""
通过ADB获取设备的Android系统版本号
返回: 版本号字符串 (如"10"), 失败时返回None
"""
try:
# 构造完整的ADB命令
adb_cmd = [
'adb',
'shell',
'su -c "getprop | grep ro.build.version.release"'
]
# 执行命令并捕获输出
result = subprocess.run(
adb_cmd,
capture_output=True,
text=True,
timeout=15,
check=True
)
# 提取版本号 (匹配方括号内的内容)
match = re.search(r'\[([^\]]+)\]$', result.stdout.strip())
if match:
return match.group(1)
else:
print("未找到版本号,原始输出:", result.stdout)
return None
except subprocess.CalledProcessError as e:
print(f"命令执行失败 (错误码 {e.returncode}): {e.stderr.strip()}")
print("请确认: 1) ADB已配置 2) 设备已连接 3) 设备已root")
except subprocess.TimeoutExpired:
print("命令执行超时,请检查设备连接")
except Exception as e:
print(f"未知错误: {str(e)}")
return None
if __name__ == "__main__":
version = get_android_version()
if version:
print(f"设备Android版本: {version}")
else:
print("获取版本号失败")
```
### 关键说明:
1. **命令结构**:
```python
adb_cmd = ['adb', 'shell', 'su -c "getprop | grep ro.build.version.release"']
```
- `adb shell`: 进入设备shell环境
- `su -c "..."`: 以root权限执行组合命令[^1]
- `getprop | grep ...`: 获取系统属性并过滤版本号
2. **版本提取逻辑**:
```python
re.search(r'\[([^\]]+)\]$', result.stdout.strip())
```
- 使用正则匹配输出中最后一个`[]`内的内容(即版本号)
- 示例输出:`[ro.build.version.release]: [10]` → 提取`10`
3. **错误处理**:
- 设备未root时`su`命令会失败(错误码127)
- ADB未连接/未授权时抛出`CalledProcessError`
- 命令超时处理(建议值15秒)
### 使用要求:
1. **设备已开启USB调试**(开发者选项中)
2. **设备已root**(`su`命令可用)
3. **电脑已配置ADB环境变量**
4. **首次连接需授权**(设备弹窗点"允许")
### 替代方案(非root设备):
若设备未root,可使用普通权限命令:
```python
adb_cmd = ['adb', 'shell', 'getprop', 'ro.build.version.release']
# 直接返回版本字符串(如"10")
```
### 输出示例:
```
设备Android版本: 10
```
### 常见问题解决:
| 错误现象 | 解决方案 |
|---------|----------|
| `adb: command not found` | 安装[ADB驱动](https://developer.android.com/studio/releases/platform-tools)并配置PATH |
| `error: no devices/emulators found` | 检查USB连接/`adb devices`授权 |
| `/system/bin/sh: su: not found` | 确认设备已root |
| 命令无输出 | 尝试直接执行`adb shell getprop ro.build.version.release` |
[^1]: 关于`su -c`的详细用法,可参考ADB命令文档中的特权命令说明
[^2]: 系统属性格式参考`getprop`输出规范,如`[ro.build.version.release]: [10]`