微信把文件都改成了只读,我找 ChatGPT 问到了药方
按:本文记录了作者在没有过多终端脚本基础的情况下,通过「咨询」ChatGPT 从无到有实现自动修复微信文件权限的过程。如果你对终端环境比较熟悉,文中提到的步骤和方法可能有些基础,解决方案也不一定是最优的。但我们认为这个探索过程是很有意义的,也体现了生成式 AI 与自动化结合的应用场景,值得推荐。如果你对文中提到的 launchd 的原理及其用例有进一步兴趣,可以阅读我们此前的一篇文章。
如果你需要本文方案的最简化、一次性实现,可以在终端运行:
find "$HOME/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat" -type f -path '*/*/Message/MessageTemp/*/File/*' -exec chmod 644 {} \;
不知道从什么时候开始,在 macOS 上微信收到的文件都变成了只读,每次打开新文件,都需要另存一份。对于每天都需要用微信收发大量 Word 文档的我,这个改动非常折磨人。当然,最好的办法是不用微信,但这显然不是打工人可以决定的。
好在,经过一些研究,我发现这个问题还是能够简单解决的,这里也将探索的过程分享给大家。需要说明的是,我不是相关方面的专业人士,本文所展示的几种方法未必是最合适的解决方案,特别是相关脚本是否存在风险还请自行判断。
此前,我曾经在网上学过一个将 Word 文档保存为 PDF 文件的快速操作(Quick Action)指令,用途是批量将 Word 转换为 PDF 文档。因此,这次我首先也想到通过 macOS 系统里自带的自动操作(Automator)来实现这个目标。
修改 Word 文档等文件的权限,至少有三种方法。第一种是打开文档然后复制或另存文件;第二种是对文档右键选择「显示简介」,拉至下方将用户的权限修改为读和写(Read & Write)。
第三种方法看起来更像是适合批量操作的,用到的是 chmod 命令,具体而言,打开终端(Terminal.app),输入
chmod 644 文件路径
即可赋予用户读写的权限。
其中,644 三个数字分别对应用户(所有者)、用户组和其他(所有人)的权限。4 代表读的权限,2 代表写的权限,1 代表执行权限。用二进制写出来的话,就不难想象 6(二进制 110)可以表示读(4,二进制 100)加上写(2,二进制 010)的权限。至于文件路径,把需要的文件拖进终端就可以,注意 chmod、644 与文件路径之间均有空格。
知道这个命令以后,我开始写快捷操作。一开始,我按照之前 Word 文档转 PDF 文件的经验使用了 AppleScript,告诉(tell)终端(Terminal.app)去运行这行 chmod 命令。问题是,不仅运行时候会有个终端弹出,运行结束后终端也不会关闭,一直保留在 dock 栏上,即使按照网上的说法,使用 exit 命令加上修改 profiles 也不能完全解决。
奇怪于网络上为什么几乎没有人遇到相似问题,我才在检索中发现,根本没有必要使用 AppleScript 打开终端,在自动操作中运行一个 Shell 脚本就可以了。完整的操作过程如下:
echo "$f"
这一行替换为 chmod 644 "$f"
就可以了。整体如下图所示:到这一步,问题得到了初步解决,在每次保存微信文件后,我只需要通过右键点击目标文件,再从快捷操作中选择上面创建的内容,就可以让随后打开的文档不再是「只读」。只不过,这样仍然有点麻烦,特别是忘记这一操作径直打开文档时,仍然有不通畅的感觉。
我的想法是,当我将微信中的文件保存到某个文件夹,如果这个文件夹里发生的变化能够被监测到,而发生变动后系统就自动运行一次上面的授权脚本,那么就不需要我手动点击快速操作按钮。
从这一步开始,我想到了 ChatGPT。过去,我拿 ChatGPT(GPT3.5 版本)测试回答工作中遇到的法律问题,得到的结果并不令人满意。但考虑到 ChatGPT 在分析代码方面的能力,我非常期待它在帮助我这样外行人时的效果。结果是,ChatGPT 的表现令我感到惊喜,在很多时候比搜索得到的结果更贴合我的问题,也更详细和耐心。
不过,在部分问题中,ChatGPT 仍然会犯错,需要自行甄别或通过网络去检索更多和更新(尤其是在 ChatGPT 数据集截止日期以后)的解决方法。
我询问 ChatGPT 的第一个问题是在 macOS 中是否有监控文件夹内文件发生变动就运行脚本的方法,ChatGPT 告诉我 fswatch 工具可以实现这个目标。
ChatGPT 告诉我 fswatch 需要通过 homebrew 安装,fswatch 的 GitHub 页面 也给出了其他安装方法。在安装 homebrew(下载方式见官网链接)后,在终端中输入 brew install fswatch
即可安装 fswatch。
只是,在对话过程中,ChatGPT 偶尔会忘记我在使用 macOS 这一前提,比如给我推荐 inotifywait 工具,在我尝试失败并检索后才发现,这是一个 Linux 上的命令。
在脚本中,只需要输入 fswatch 文件路径
就可以监控指定文件夹,同时,也可以添加一些更细化的参数以选择监控文件的范围。在这方面问题上,可能由于 fswatch 自身的版本更新,ChatGPT 有时候能够迅速解决了我的问题,但有时候也会犯错导致我走弯路。
比如,我希望只在添加文件时触发事件,ChatGPT 明确告诉我可以使用 --event Create
参数来实现。
但在我想要使用 -i
参数将添加的文件类型限制于 .docx 等文档时,ChatGPT 就明显弄反了 -i
(include,包含)和 -e
(exclude,排除)两个参数。
考虑到授权陌生文件可能带来的风险,如何只监控特定文件恰恰是我比较关注的事项(当然,都监控然后加一个文件名判断也不是难事)。ChatGPT 在这个问题上没有给我什么帮助。这里需要特别记录一下的是,fswatch 命令中,只写 -i
并不意味着可以只监控符合规则的文件,必须首先写上 -e
并排除一切文件,再写一个 -i 包含需要的文件,才能够实现监控符合条件文件的目标。
上图的另一个错误在于,fswatch 使用的匹配规则并非通配符,而是正则表达式(regex),当然这也可能是由于 ChatGPT 使用过时资料库导致的。在正则表达式中,匹配任意单字符的不是 *
而是 .
,任意数量字符则是 .*
。在学习和测试后,我将匹配部分写成如下形式:
# 文件类型,可随意修改添加
types=("doc" "docx" "ppt" "pptx" "xls" "xlsx")
# 生成正则表达式
re=""
for i in ${types[*]};do re="$re|$i$";done
re=${re:1}
# 监控满足条件的文件变动
fswatch -e ".*" -EIi $re --event Created $path
其中,re
经过循环后生成的值为 doc$|docx$|ppt$|pptx$|xls$|xlsx$
,表示匹配以 doc、docx 等结尾的文件名。顺便一提,Shell 在赋值的时候,= 左右侧不能加空格,这写起来也太难受了。
fswatch 的参数中,-e ".*"
表示排除所有(.*
)文件。 -EIi $re
表示包含上述文件类型的文件,E(extended)和 I(insensitive)的含义分别为使用扩展的正则表达式(否则无法使用或逻辑 |
),以及不区分大小写。$path
则是目标文件夹,我事先定义了文件路径。
监控文件发生变化后,在如何修改变动文件权限的问题上,我一开始又使用了笨方法。我询问 ChatGPT 如何找到最后添加到文件,它在两次提问中分别给了我使用 ls 命令和 find 命令的方法,但我测试后发现这似乎找到的是最后修改文件,而非最后添加文件,这与我的需求不太符合。于是我使用了 find $path -type f -perm 444
来查找整个文件夹内权限为「444」(只读)的文件并批量进行修改。
但当我返回查看 ChatGPT 最初给我的示例时,我意识到我可能把事情想复杂了。
虽然暂时不知道 |
的具体作用,但上面这行示例代码的意思显然是,fswatch 发现变动后,通过 while 循环和 echo 命令显示变动的文件,这看起来很符合我的要求。为此,我认真学习了管道 |
的用法……才怪咧,并没有那么多时间。显然,这里我只需要把 echo $file
修改为 chmod 644 $file
就可以了。
在 read file
的过程中遇到了一个小问题,就是读取的文件名中如果有空格那么就会截断为两个变量。通过检索,将环境变量 IFS
修改为 $'\n'
(换行)就能够让 read
变为逐行读取,解决这个问题。
整个脚本的代码展示如下:
# ChatGPT 说,这是一个称为 shebang 的特殊注释,用于指定脚本解释器的路径
# 如果该注释不存在或指定的路径不正确,则操作系统将尝试使用默认的解释器来执行脚本
#!/bin/bash
# 自定义路径,这里以 download 文件夹为例
# 实际上应该也可以直接修改为微信的缓存文件夹(可以通过右键点击微信内接收到的文件查看路径)
path="/Users/用户名/Downloads"
# 生成正则表达式,用于匹配文件
types=("doc" "docx" "ppt" "pptx" "xls" "xlsx")
re=""
for i in ${types[*]};do re="$re|$i$";done
re=${re:1}
# 修改环境变量,让后面的 read 能够逐行读取
IFS=$'\n'
# 监控添加文件的事件,将变动文件传递至下面的 chmod 命令
fswatch -e ".*" -EIi $re --event Created $path |
while read file;do
# 额外增加一个文件存在和不可写的判断,排除打开 Word 文档时产生的临时文件,避免报错
if [ -f $file ] && ! [ -w $file ];then
chmod 644 $file
fi
done
最后,将文件保存为 自定义名字.sh
,并在终端里用 chmod
命令将脚本文件的权限至少修改为 744
(可读写和执行)。然后,就可以将它拖入终端中回车运行。
问题和麻烦从此刻开始不断涌现。
在写完以上脚本后,我打算让脚本自己跑起来,并在开机自动启动,ChatGPT 推荐使用 launchd
工具。
可在我模仿上面的文件修改完毕(实际上只需要复制文件夹内随便一个其他文件就可以)后,出现了各种问题,包括但不限于:
launchctl unload
卸载 plist 文件后 launchctl load
重新加载文件即可。744
是不可以的,必须得 644
。/Library/LaunchDaemons
,但那是系统级别的守护进程所在位置,放在那里会提示权限不足,实际上应该放在 ~/Library/LaunchAgent
,以当前用户身份运行就行了。launchd
启动不生效,但在终端中启动就可以——原因是 launchd
启动的进程的环境变量 PATH
与交互式终端下不同,无法找到 fswatch,解决方式是找到 fswatch 命令的绝对路径,放在脚本里。chmod
报错权限不足——一开始我听信了 ChatGPT 的说法,以为需要在脚本中加上 sudo
,可那就意味着每次都需要输入密码,这显然是不符合我自动运行脚本需求的。好在我最后在一个论坛里发现了解决方法,通过赋予 bash 完全磁盘访问权限(full disk access),可以让通过 launchctl
运行的脚本中的 chmod
命令有权限修改其他文件的权限。具体步骤还是可以问到:
至此,我终于有了一个可以自动启动的,不断监控目标文件夹的脚本。让我在微信自己解决这个问题以前,免受只读文件之苦。
如后文所述,因为不太想设置自动启动,我自己最后没有用这套方案。但如果有人想要用这套方案,请参考以下步骤为相关脚本提供权限,否则会无法生效:
~/Library/LaunchAgents
文件夹,复制一份 plist 文件,重命名为「xxx.plist」。打开后参照上面 ChatGPT 给出的示例,删去不需要的部分,修改 Label
和 ProgramArguments
字段中 <string>
和 </string>
包围的内容,前者就是一个单纯用于区分的名称,后者则修改为上面 xxx.sh
脚本的路径。launchctl load -w xxx.plist
后回车,其中 xxx.plist
替换为第一步创建的 plist 文件的完整路径(将文件拖入即可)。xxx.sh
脚本中的 fswatch
,替换为运行 which fswatch
命令找到的绝对路径,并重新加载一遍 plist 文件(先用 launchctl load xxx.plist
卸载)。在上面询问 ChatGPT 如何赋予 bash 完全磁盘访问权限时,ChatGPT 提到了「存在一定的安全风险」。也许有的读者会介意这里的「安全风险」,因此我也就不推荐所有人采用上面的 launchd 方案。
我回想了一下平时的电脑使用习惯,我平时以待机为主,不经常关机,开机自动启动脚本也不是非要不可,只要启动后可以安静地自动运行就够了。所以,我又开始探索是否可以免去自动启动的要求。
在这里,我又走了一点弯路,我回到了自动操作(Automator),试图通过打包一个应用程序(Application)来实现目标,但运行时显示在上方状态栏的齿轮符号让我打消了这个念头。
于是,我问了 ChatGPT 最后一个问题,它的回答也一如既往的简洁和清楚。
当然,我自己是记不住 nohup
命令后面那几个参数的,所以我又写了一个新的 .sh 文件存放这条启动脚本的命令。等开机后,就将这个文件通过终端运行一次,然后就可以退出终端,让修改权限脚本保持运行。与之对应的是,我也写了一条关掉脚本的指令备用,存在同一个文件夹的另一个 .sh 脚本中。
这两个文件分别为,启动:
#!/bin/bash
# 启动 xxx.sh,替换为正确路径即可
nohup xxx.sh >/dev/null 2>&1 &
关闭:
#!/bin/bash
# 关闭 xxx.sh,替换为正确文件名即可
pkill -f xxx.sh
在此之前,我从来没有写过 Shell 脚本,否则也不会在一开始干出在自动操作中 tell "Terminal.app"
的事情。虽然学习过程中也遇到了不少问题,但整体上还是比较愉悦和流畅的。一个原因可能在于,对于从零开始的学习者而言,ChatGPT 在每个问题上都提供了详尽有用的解释和非常具体的示例,非常直观和有效。
当然,ChatGPT 好用也不意味着可以对其无条件信任,在解决问题时,查看文档和搜索引擎仍然是有必要的。并且,比起高度抽象地描述问题让它给出完整方案,一步步提问可能对学习和检验都更有帮助。
一点额外的感想是,就像我在 ChatGPT 中提问自身专业知识时,很容易看出它从什么时候开始胡编乱造,相信计算机专业人士在使用 ChatGPT 中也能更高效地甄别信息的真伪,更直接地去利用那些更有价值的东西,扬弃错误和冗余的部分。我想,这某种程度上或许也说明了专业人士并不那么容易因 AI 发展而被替代,反而是比起一般人在利用 AI 解决相关问题时更有优势吧。