删除markdown文件里面没有引用的图片小工具_cos 图床未引用的图片如何删除_张俊杰1994的博客-程序员宅基地

前言

markdown 插入图片默认是放到本地的, 如果你在markdown文件里面删除了图片,但是本地图片是还存在的,
于是我写了个小工具删除这个没有用的图片

原理

其实就是遍历电脑上所有的markdown文件, 找出markdown里面的图片, 和我typora设置保存图片的地址里面的所有的图片去匹配,如果markdown没有typora的图片的引用, 就认为是废弃的图片,就把这个图片改个名字,后面拼接一个待删除的后缀,再剪切到别的文件夹里面,为什么不直接删除,目的就是防止程序出现bug导致的误删. 如果真不小心误删除了, 直接给图片后缀"待删除"删掉,放到markdown图片,还能接着使用.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <commons-io.version>2.6</commons-io.version>
        <commons-lang3.version>3.9</commons-lang3.version>
        <logback-classic.version>1.2.3</logback-classic.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>${commons-io.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${commons-lang3.version}</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback-classic.version}</version>
        </dependency>
    </dependencies>

    <build>
        <!--<pluginManagement>&lt;!&ndash; lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) &ndash;&gt;-->
        <plugins>

            <plugin>
                <!--<groupId>org.apache.maven.plugins</groupId>-->
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

            <plugin>
                <artifactId>maven-assembly-plugin </artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifest>
                            <mainClass>com.bd.util.appclient.AppMain</mainClass>
                        </manifest>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>


        </plugins>
        <!--</pluginManagement>-->
    </build>

</project>

logback.xml 放到resources目录下

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- 定义日志文件的输出路径 -->
    <!-- <property name="USER_HOME" value="G:/log" /> -->

    <!-- 输出到控制台 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>

    <!-- 基于大小以及时间的轮转策略 -->
    <!-- 参考:http://www.logback.cn/04%E7%AC%AC%E5%9B%9B%E7%AB%A0Appenders.html -->
    <appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 要写入文件的名称。如果文件不存在,则新建。 -->
        <!-- <file>${USER_HOME}/logback.log</file> -->
        <file>logback.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>%d{yyyyMMdd}/logback-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>100KB</maxFileSize>
            <!-- 最多保留多少数量的归档文件,将会异步删除旧的文件。 -->
            <maxHistory>30</maxHistory>
            <!-- 所有归档文件总的大小。当达到这个大小后,旧的归档文件将会被异步的删除。 -->
            <totalSizeCap>1000MB</totalSizeCap>
        </rollingPolicy>

        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %-5level [%-10thread] %logger - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 日志输出级别 -->
    <root level="info">
        <appender-ref ref="ROLLING" />
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

主程序

package com.zjj;

import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PictureClear {
    
	static ArrayList<String> targetPath = new ArrayList<>();
	static Logger logger = LoggerFactory.getLogger(PictureClear.class);

	//这个列表是有图片的markdown.
	static HashSet<String> markdownNameSet = new HashSet<>();
	//所有markdown里面所有的图片
	static HashSet<String> pictureNameInMarkdown = new HashSet<>();
	// 图床路径
//	public static String figureBedPath = "D:\\Users\\微云同步助手\\软件配置备份\\typora图片存储\\";
	public static String figureBedPath ;
	// 删除目录
//	public static String toDeletePath = figureBedPath + "toDelete" + "\\";
	public static String toDeletePath;
	public static void main(String[] args) throws IOException {
    


		long start = System.currentTimeMillis();
		init(args);

		//获取带图片的md文件列表
		doSearchBandPicMD();

		//微云所有的图片
		HashSet<String> weiyun = getFigureBedFileList();
		// 将不匹配的图片重命名并且挪到 toDelete文件夹里面
		doReName(pictureNameInMarkdown, weiyun);
		System.out.println("完成时间:" + (System.currentTimeMillis() - start));

	}

	private static void init(String[] args) {
    
		// 赋值
		PictureClear.figureBedPath = args[0];  //图床路径
		PictureClear.toDeletePath = args[1];  // 删除路径

		String arg = args[2];
		String[] split = arg.split(",");
		for (String s : split) {
    
			targetPath.add(s + ":\\");
		}

//		targetPath.add("E:\\");
//		targetPath.add("D:\\");


		if (!new File(toDeletePath).exists()) {
    
			new File(toDeletePath).mkdirs();
		}
	}

	private static void doSearchBandPicMD() throws IOException {
    
		for (String path : targetPath) {
    

			File[] files = new File(path).listFiles();
			for (File file : files) {
    
				if (file.isHidden()) {
      // 隐藏文件直接跳过
					continue;
				}
				logger.info("开始检索目录 :" + file.getPath());
				if (file.isFile()) {
    
					String name = file.getName();
					String[] split = name.split("\\.");
					String fileNameNow = split[split.length - 1];

					if ("md".equals(fileNameNow)) {
    
						doSetMarkdownNameList(file, name);
					}
				} else {
    
					doRecursionSearchMDFile(file);
				}


			}

		}

	}

	/**
	 * 递归
	 * 张俊杰 2021年01月29日 10:08
	 */
	private static void doRecursionSearchMDFile(File file2) throws IOException {
    


		File[] files = file2.listFiles();
		for (File file : files) {
    
			if (file.isHidden()) {
      // 隐藏文件直接跳过
				continue;
			}
			logger.info("开始检索目录 :" + file.getPath());
			if (file.isFile()) {
    
				String name = file.getName();
				String[] split = name.split("\\.");
				String fileNameNow = split[split.length - 1];

				if ("md".equals(fileNameNow)) {
    

					doSetMarkdownNameList(file, name);
				}
			} else {
    
				doRecursionSearchMDFile(file);
			}


		}


	}

	private static void doSetMarkdownNameList(File file, String name) throws IOException {
    
		String s1 = FileUtils.readFileToString(file, "UTF-8");
		String regex = "(!\\[.*\\])(\\(.*\\))"; // 捕获组,匹配类似于 "![*](*)" 的字符串
		Pattern pattern = Pattern.compile(regex);
		Matcher matcher = pattern.matcher(s1);
		while (matcher.find()) {
    
			String ref = matcher.group(0);
			// 获取图片名称
			int beginIndex = ref.indexOf("](") + 2;
			int endIndex = ref.length() - 1;
			String pictureName = ref.substring(beginIndex, endIndex);
			// 保存图片名称

			if (pictureNameInMarkdown.add(pictureName)) {
    
				markdownNameSet.add(file.getPath());
			}

		}


	}


	/**
	 * 获取图床文件列表, 排除掉 范围之外的.
	 * 张俊杰 2021年01月28日 18:59
	 */
	private static HashSet<String> getFigureBedFileList() {
    
		File file = new File(figureBedPath);
		HashSet<String> result = new HashSet<>();
		File[] files = file.listFiles();
		for (File file1 : files) {
    
			if (file1.isDirectory()) {
    
				String Path = file + "\\" + file1.getName();
				if (new File(Path).compareTo(new File(toDeletePath)) == 0) {
    
					continue;
				}
//				System.out.println("Path = " + Path);
//				System.out.println("toDeletePath = " + toDeletePath);
//				if (Path.equals(toDeletePath)) {
    
//
//				}

				File file2 = new File(Path);
				File[] files1 = file2.listFiles();

				for (File file3 : files1) {
    

					result.add(Path + "\\" + file3.getName());


				}
			}
		}
		return result;
	}

	private static void doReName(HashSet<String> picturesInMarkdown, HashSet<String> weiyun) {
    
//		HashSet<String> toDelDir = new HashSet<>();
		for (String wy : weiyun) {
    
			boolean flag = baohan(wy, picturesInMarkdown);
			if (!flag) {
    
				reName(wy);
			}
		}
	}

	/**
	 * 判断是否在hashset里面包含
	 * 张俊杰 2021年01月28日 18:59
	 */
	private static boolean baohan(String wy, HashSet<String> picturesInMarkdown) {
    
		boolean equals = false;
		for (String s : picturesInMarkdown) {
    

			equals = s.equals(wy);
			if (equals) {
    
				break;
			}
		}
		return equals;
	}

	/**
	 * 将文件移动到 指定名录,同时名字后面追加待删除 名字
	 * 张俊杰 2021年01月28日 18:22
	 */
	private static void reName(String wy) {
    
		//获取需要剪切的文件
		File file = new File(wy);


		String[] split = file.getPath().split("\\\\");
		String s = split[split.length - 2]; // 子目录的名字


		File targetFile = new File(figureBedPath + s);
//
		logger.info("将要重命名文件 : " + file);
		boolean flag = file.renameTo(new File(toDeletePath + "\\" + file.getName() + "待删除"));

		if (flag) {
      // 如果是空的文件夹就直接删除掉
			if (targetFile.length() == 0) {
    
				logger.info("删除了这个空文件夹: " + targetFile);
				targetFile.delete();
			}
		}
	}
}

项目Maven打包,

直接Maven package命令打包,这个就不说了.

你会发现有个 demo-1.0-SNAPSHOT-jar-with-dependencies.jar 文件

编写脚本start.cmd

set figureBedPath=D:\\Users\\微云同步助手\\软件配置备份\\typora图片存储\\
set toDeletePath=D:\\Users\\微云同步助手\\软件配置备份\\typora图片存储\\toDelete\\
set ScanningDrive=D,E


java -classpath demo-1.0-SNAPSHOT-jar-with-dependencies.jar com.zjj.PictureClear [%figureBedPath% %toDeletePath% %ScanningDrive% ]

说明, figureBedPath是 你markdown图片存储的路径,和上面typora是有对应关系的, 看上面的配置D:\Users\微云同步助手\软件配置备份\typora图片存储\${filename} , 你把 ${filename} 删掉就行了.

toDeletePath是不存在的文件要移动到哪里去,

ScanningDrive 把你要放markdown的磁盘都写上,假如说我C盘和D盘放markdown,那么就给c和d都写上… 建议给你所有盘符都写上.,

typora配置

D:\Users\微云同步助手\软件配置备份\typora图片存储${filename}
在这里插入图片描述

开始运行代码查看效果…

在这里插入图片描述

我上传的图片会到这个文件夹下面,
如果我markdown删除了图片, 我程序会扫描到发现markdown没这个图片,
就会给这个图片挪到toDelete文件夹下,
在这里插入图片描述
目的就是防止误删除, 所以放到这里,你只需要给 “待删除” 这个结尾删除就行了,图片就又能显示了.

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_41489540/article/details/113377970

智能推荐

Linux服务器NTP服务及时间同步-程序员宅基地

Linux服务器NTP服务及时间同步保证集群内时间一致性在集群环境中,我们往往很多时候没有外网,那么我们就需要在集群中选一台出来当时间服务器,所有其他服务器从是时间服务器同步时间,保证集群内的时间的一致性。NTP时间同步方式选择NTP同步方式在linux下一般两种:使用ntpdate命令直接同步和使用NTPD服务平滑同步。使用ntpdate命令直接同步的方式存在风险,比如一些定时任务在已有时间内执行过,直接同步导致时间变回任务执行前的时间段,定时任务会重复执行。使用NTPD服务平滑同步的方式不会

比较自然语言与计算机语言,计算机语言与自然语言的比较研究.pdf-程序员宅基地

文档介绍:计算机语言与自然语言的比较研究.pdf量一壁一史一—堕:......计算机语言与自然语言的比较研究黄超,张天骄,黄文君,刘洋枣庄学院信息科学与工程学院山东枣庄;沈阳音乐学院辽宁沈阳【摘要】计算机语言做为人与计算机之间交流的程序设计工具与人类日常使用的自然语言之间既有相似之处又有本质的区别,但是在学****使用、开发计算机语言及自然语言处理等领域还需要理清两者的关系,为此,对计算机语言与自..._计算机语言与自然语言的区别

一个与mysql unique key相关的问题-程序员宅基地

xxx_tab的user_id是unique key, 来看看伪代码:get user_id=10000 from xxx_tab;if exist { return ok}ret = insert user_id=10000if ret ok { return ok}return not_ok 问题: 为什么最后结果是n...

软考-软件设计师 笔记九(多媒体基础)_软考中级多媒体应用设计笔记-程序员宅基地

音频相关概念图像相关概念媒体的种类多媒体的计算问题常见多媒体标准数据压缩基础技术有损压缩与无损压缩内容提要音频相关概念图像相关概念媒体的种类注意显示媒体(表示媒体):表现和获取信息的物理设备。多媒体的计算问题例题1:颜色深度24位说明一个像素点需要24个bit位存储 = 3 字节,每张照片的容量 = 1600 * 1200 * 3 = 5760000字节,5760000/1024/1024=5.493MB,128/5.493 = 23.3 ⇒ 23张例题2:双_软考中级多媒体应用设计笔记

华为 配置 MAC认证优先 Portal认证 Mac无感知快速认证 Radius认证计费 对接 外部 Portal认证计费系统 案例_华为portal免认证 绑定mac-程序员宅基地

华为 配置 MAC认证优先 Portal认证 Mac无感知快速认证 Radius认证计费 对接 外部 Portal认证计费系统 案例介绍:OpenPortal网络准入认证计费系统,支持用户名密码认证、短信认证、钉钉授权认证、微信认证、公众号认证、答题认证、视频倒计时认证、人脸识别认证、访客二维码授权认证、LDAP AD域结合认证、第三方OA系统扩展认证等等各种认证模式,支持二次代拨认证等技术,支持用户自助注册,自行选择计费套餐进行支付宝、微信自助缴费等。 支持与华为..._华为portal免认证 绑定mac

随便推点

Kubernetes YAML最佳实践和策略_或者使用官方kubebiz提供的yaml源-程序员宅基地

Kubernetes工作负载最常用YAML格式的文件来定义。YAML的问题之一就是很难描述清单文件之间的约束或关系。如果你希望检查是否已从受信任的注册表中提取部署到群集中的所有映像,该怎么办?如何防止没有Pod安全策略的工作负载提交到集群?集成静态检查可以在更接近开发生命周期的时间内捕获错误和违反策略的行为。并且由于改善了资源定义的有效性和安全性,因此你可以相信生产工作负载遵循最佳实践。Kubernetes YAML文件的静态检查生态系统可以分为以下几类:API验证程序:此类工具针对Ku_或者使用官方kubebiz提供的yaml源

oracle 31640,impdp时遇到ORA-31693&ORA-31640&ORA-19505&ORA-27037_王土三的博客-程序员宅基地

impdp时遇到ORA-31693&ORA-31640&ORA-19505&ORA-27037在impdp时遇到报错:Additional information: 3. . 导入了 "HQ_X1"."T_XX130901":"SP15" 13.99 KB 0 行. . 导入了 "HQ_X1"."T_XX130901":"SP2" 13.99 KB ..._ora31640

使用spring-data-redis对redis进行操作_spring.redis.database-程序员宅基地

前面介绍了Redis的安装配置,今天我将介绍通过spring-data-redis实现java程序对redis进行操作。对于redis的介绍我就不细说,在我们写程序的时候就是要构建好我们的key-value对,然后利用spring-data-redis将我们构建好的key-value保存到redis中。spring-data-redis官方提供了很多种应用方式:与spring cache集成、r..._spring.redis.database

2005岁末BLOG程序大评点(转)_lbs^2 asp-程序员宅基地

ASPL-Blog: http://www.loveyuki.com由Loveyuki自主开发的基于 ASP+Access 的小型单用户BLOG,目前似乎已经停止更新了,但是用户群相当大,而且是国内相当多的BLOG系统的鼻祖。oblog: http://www.oioj.net多用户Blog,目前占据ASP多用户BLOG的大部分市场,2.X商业版已经实行免费,很值得继续关注与期待的国内作品。SLb_lbs^2 asp

Android之通过Url保存图片集到本地并通知图库刷新-程序员宅基地

Android之通过Url保存图片集到本地并通知图库刷新直接上代码 疑问联系774908055public static boolean saveBitmaps(Context context,List<Bitmap> bitmaps, String rootPath) { LogUtil.LogShitou("创建PIC = " + rootPath); ...