Mac OS X下Python调用shell脚本解压ZIP致应用损坏无法打开
运行环境
- Mac OS X Catalina 10.15
- Python 3.10
问题背景
基于Python开发Mac OS应用,使用Py2App打包生成MySoftware.app,需要实现自动更新逻辑:自动下载新版app,删除/Applications下的旧版本,再将新版本复制到该目录。
先后尝试了三种应用分发方案:
- 使用packages制作pkg安装包
- 制作dmg镜像,挂载后提取文件到/Applications目录
- 将应用打包为zip文件,解压到/Applications目录
以上方案手动操作均正常,但通过Python脚本自动化执行时,双击更新后的应用会弹出错误提示:You can't open the application "MySoftware" because it may be damaged or incomplete.(无法打开应用“MySoftware”,因为它可能已损坏或不完整)。
最初编写的自动更新bash脚本逻辑如下:
- 下载包含新版MySoftware.app的zip压缩包
- 解压压缩包
- 删除/Applications路径下的旧版MySoftware.app
- 通过
cp -R命令将新版应用复制到/Applications目录
AutoUpdate.sh脚本内容
#!/bin/bash echo "------------- STARTING AutoUpdate.sh -----------------" # If MySoftware.app doesn't exist, we unzip MySoftware_MAC.zip if [[ ! -d "MySoftware.app" ]];then echo "The directory MySoftware.app doesn't exist" if [[ -f "MySoftware_MAC.zip" ]];then unzip MySoftware_MAC.zip echo "The file MySoftware_MAC.zip has been unzipped" else curl -sSL https://www.MySoftware.co/dist/MySoftware_MAC.zip --output MySoftware_MAC.zip unzip MySoftware_MAC.zip echo "The file MySoftware_MAC.zip has been unzipped" fi fi rm -rf /Applications/MySoftware.app echo "The folder /Applications/MySoftware.app has been removed" #curl -sSL https://www.MySoftware.co/dist/MySoftware_MAC.zip --output MySoftware_MAC.zip #echo "The file MySoftware_MAC.zip has been downloaded" #unzip MySoftware_MAC.zip cp -R MySoftware.app /Applications echo "The folder MySoftware.app has been copied pasted to the folder /Applications" osascript -e 'quit app "MySoftware"' echo "The application MySoftware has been Quit" open /Applications/MySoftware.app/Contents/MacOS/MySoftware echo "The new application /Applications/MySoftware has been open"
直接在终端执行该shell脚本时运行完全正常,更新后的应用可正常启动,但通过Python脚本调用时会复现故障。
Python调用代码
down_url = f"http://www.MySoftware.co/dist/MySoftware_MAC.zip" urllib.request.urlretrieve(down_url, MySoftware_MAC.zip) down_url = f"http://www.MySoftware.co/dist/AutoUpdate.sh" urllib.request.urlretrieve(down_url, 'AutoUpdate.sh') os.system(f"chmod 755 'AutoUpdate.sh'") command_run_autopupdate_shell_sript = f"""/usr/bin/osascript -e 'do shell script "/bin/bash 'AutoUpdate.sh' >> {os.environ['HOME']}/log_AutoUpdate.log" with prompt "Software need to install some programs." with administrator privileges'""" proc = subprocess.Popen(command_run_autopupdate_shell_sript, stdout=subprocess.PIPE, stderr=subprocess.PIPE,shell=True) (output, err) = proc.communicate() logger.info(output.decode("utf8")) logger.info(err.decode("utf8")) returncode_command_install_Software = proc.wait() logger.info(f"returncode_command_install_software : {returncode_command_install_Software}")
即使用root权限执行脚本,问题依然存在:脚本无任何错误提示,返回码为0,但更新后的应用图标显示异常,双击报损坏/不完整错误。
returncode_command_install_software : 0
故障原因
问题由三个核心问题共同导致:
- 相对路径失效:通过
osascript提权执行shell脚本时,进程的工作目录为root用户的家目录/var/root,而非下载更新包、脚本所在的应用运行目录,脚本里用相对路径找MySoftware.app、MySoftware_MAC.zip时实际找不到文件,cp -R执行时未正确复制完整的app bundle,但因为命令本身没触发语法错误所以返回码为0。 - 复制命令不兼容macOS app结构:
cp -R命令不会保留macOS app bundle所需的符号链接、文件权限、扩展属性,Py2App打包的应用内部存在大量框架相关的符号链接,普通复制会导致链接断裂、文件权限丢失,直接造成app结构损坏。 - Gatekeeper隔离属性拦截:通过网络下载的文件会被系统自动加上
com.apple.quarantine(隔离)扩展属性,手动操作时系统会弹出安全提示走完校验流程,但自动化复制后直接启动时,Gatekeeper检测到未经过校验的app带隔离属性,会直接判定为损坏拦截启动。
解决方案
按以下步骤修改即可解决问题:
- 修正Python调用逻辑,在调用脚本时传入更新资源所在的绝对路径,避免相对路径失效。将Python里的执行命令改为先切到资源所在目录再执行脚本,示例:
import os res_dir = os.path.abspath(os.path.dirname(__file__)) # 替换为存放下载的zip、sh脚本的实际绝对路径 command_run_autopupdate_shell_sript = f"""/usr/bin/osascript -e 'do shell script "cd {res_dir} && /bin/bash ./AutoUpdate.sh >> {os.environ['HOME']}/log_AutoUpdate.log 2>&1" with prompt "Software need to install some programs." with administrator privileges'"""
- 替换shell脚本里的复制命令,用macOS原生的
ditto命令复制app bundle,该命令会自动保留符号链接、权限、扩展属性,把原来的cp -R MySoftware.app /Applications替换为:
ditto MySoftware.app /Applications/MySoftware.app
- 复制完成后移除app的隔离属性,在复制完成、启动应用前加入以下命令:
xattr -dr com.apple.quarantine /Applications/MySoftware.app
- 修正启动命令,不要直接调用app内部的Mach-O二进制,用系统的Launch Services启动app本体,把原来的
open /Applications/MySoftware.app/Contents/MacOS/MySoftware替换为:
open /Applications/MySoftware.app
修改完成后重新打包测试,自动更新流程即可正常运行,不会再出现应用损坏的提示。
内容的提问来源于stack exchange,提问作者Gauthier Buttez
相关产品推荐
相关产品推荐

