分析内容
众所周知,npm下载的node_modules文件夹是可以直接复制剪切的,但是pnpm下载的不行
本文写了一个脚本处理了这个问题
事先声明,代码执行要好几分钟,效率上不如直接pnpm i拉取(执行很快)
该文件夹根内容大概内容分三类:

①.bin文件夹是很多脚本文件,作用不明,含有pnpm i时下载的路径,但不影响运行
②.pnpm文件夹包含所有module,里面也包括了少数的目录联接
③·根目录的其它几十个文件夹(长得像快捷方式)都是目录联接
所有的目录联接都指向了.pnpm里面的某些文件夹
Windows 中的硬链接、目录联接(软链接)、符号链接、快捷方式-阿里云开发者社区
这些目录联接A指向了另一个目录B,A内部的元素和B是同一份,但点进A文件夹并不会像快捷方式一样跳转到B,A是B的别名。且A不能被剪切和复制(这样做只能获得一个普通文件夹A',复制时A'是空的,剪切时不会删除A,但会把B的全部内容剪切到A')
可以通过命令行fsutil reparsepoint query "path" 或 python的win32file.GetFileAttributes(path) 判断一个目录是否是目录联接,后者因为不是命令,在python执行更快
可以通过mklink /J "new_dir" "target"来创建指向target的目录联接
因此,流程为:
1、把该文件夹从A项目复制到B项目
2、遍历A项目的文件夹,找出所有联接目录,记录若干组位置和指向位置
3、把每一组位置和指向位置在/node_modules之前的部分替换为B项目所在的位置,删除B中复制失败的空文件夹,并在相同位置创建新的目录联接
代码:
其实还有很多bug,但是能满足要求,我不想改了:
1、运行结束后,会再打开界面重新运行一遍程序,可以ctrl+c取消
2、莫名其妙出现很多重复的操作,都会在第二次执行到时提示出错
3、其实创建联接(/J)不需要管理员权限,但是创建符号链接(/D)需要,所以创建管理员cmd那部分可以删掉。fsutil reparsepoint query输出符号链接的指向时,输出内容处理起来有点麻烦
本文是把C:\Users\HP\Desktop\web\demo-vue-master\node_modules复制到C:\Users\HP\Desktop\web\test\demo-vue-master\node_modules,具体替换内容可修改
import subprocess
import ctypes, sys
import os
import time
import win32file
def check_reparse_point(path):
# 检查路径是否是联结点
if win32file.GetFileAttributes(path) != 1040:
return None
command = ['fsutil', 'reparsepoint', 'query', path]
output = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
if output.returncode != 0:
print(f"命令执行出错:{output.stderr.decode('GBK').strip()}")
return None
else:
lines = output.stdout.decode('GBK').strip().splitlines()
location = lines[9].rsplit(' ', 1)[1]
return location
def find_reparse_point(path):
global total_num
global temp
reparse_points = []
reparse_points_target = []
for file in os.scandir(path):
if os.path.isdir(file):
check = check_reparse_point(file.path)
if check != None:
reparse_points.append(file.path)
reparse_points_target.append(check)
total_num += 1
else :
point,target = find_reparse_point(file)
reparse_points += point
reparse_points_target += target
if total_num % 50 == 0:
if total_num > temp:
temp = total_num
print(total_num,path)
return reparse_points,reparse_points_target
def execute_command(command):
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE,shell=True)
output,error=process.communicate()
if process.returncode!= 0:
print(f"命令执行出错:{error.decode('GBK').strip()}")
return None
return output.decode('GBK').strip()
def generate_link(link,target):
if os.path.exists(link):
if not os.listdir(link):
try:
os.rmdir(link)
except OSError as e:
print(f"删除文件夹 '{link}' 时出错:{e}")
else:
print(f"文件夹 '{link}' 存在且不为空,无法删除。")
return
if os.path.exists(target):
# J是联结,D是符号
batch_command = ['mklink','/J', link, target]
result = execute_command(batch_command)
if result is not None:
# print(f"{result}")
pass
else:
print("链接创建失败。")
else:
print("目标文件夹",target,"不存在。")
def is_admin():
try:
if not ctypes.windll.shell32.IsUserAnAdmin(): # 如果不是管理员窗口,返回0
# 以管理员权限重新运行程序
ctypes.windll.shell32.ShellExecuteW(None,"runas", sys.executable, __file__, None, 1)
except:
print('error when check admin')
exit(0)
def main():
is_admin()
global total_num
source = R"C:\Users\HP\Desktop\web\demo-vue-master\node_modules"
start_time = time.time()
replace_pre = R"web\demo"
replace_target = R"web\test\demo"
try:
reparse_dirs,reparse_dirs_source = find_reparse_point(source)
print(total_num)
for i in range(len(reparse_dirs)):
target_folder = reparse_dirs_source[i].replace(replace_pre, replace_target)
linked_folder = reparse_dirs[i].replace(replace_pre, replace_target)
generate_link(linked_folder,target_folder)
except KeyboardInterrupt:
print("发现的总数",total_num)
print("耗时:",time.time()-start_time,"秒")
if __name__ == '__main__':
total_num = 0
temp = 0
main()
附加:执行fsutil reparsepoint query的反馈
对于联接目录,输出为:

对于符号链接目录,输出为:

3584

被折叠的 条评论
为什么被折叠?



