删除markdown文件里面没有引用的图片小工具_张俊杰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

智能推荐

c语言a=13 b=6a gt gt 2,Python 基础【二】 上_意禹的博客-程序员宝宝

一、python语言分类1. C pythonc语言的python版本 官方推荐使用C语言实现,使用最为广泛,CPython实现会将源文件(py文件)转换成字节码文件(pyc文件),然后运行在Python虚拟机上。2. Jython java pythonPython语言的Java实现,不仅提供Python的库,同时也提供所有的Java类。能运行在任何可兼容的Java1.1或更高的Java虚拟...

wso2_使用WSO2 ESB进行邮件内容过滤_cunfeng7797的博客-程序员宝宝

wso2 每个集成架构师或开发人员都应该熟悉Gregor Hohpe和Bobby Woolf所描述的企业集成模式(EIP) 。 模式之一是“内容消息过滤器” (不要与消息过滤器模式混淆)。使用不同的Mediator在WSO2中有多种方法可以实现此目的。 一种方法是使用XSLT介体,您可以在其中简单地使用XSLT进行过滤。 另一个(根据其名称不太明显)是Enrich Mediator 。 这...

Android 8.0系统源码分析--openCamera(HAL)启动过程源码分析_红-旺永福的博客-程序员宝宝

     前面我们详细分析了从应用层调用CameraManager的openCamera的方法来打开相机的逻辑,上次的分析我们来到了CameraServer进程当中,但是还没有真正看到open操作设备节点来实现真正打开的逻辑,遗留的问题也就是从frameworks\av\services\camera\libcameraservice\device3\Camera3Device.cpp文件中的st...

移植Linux 2.6.30.4到mini2440(Kernel)_捷杰耶夫的博客-程序员宝宝

作者:micheal7500转自:http://blog.chinaunix.net/uid-20539088-id-115747.html前言:    尽管linux-2.6.31直接提供了对mini2440的支持,但移植过程中发现存在的问题比较多,所以最后决定使用linux-2.6.30.4来完成这次移植过程!0)、准备工作交叉编译器使用codesour

lightGBM与XGBOOST相关概述_lightgbm xgboost_tomwang0322的博客-程序员宝宝

LightGBM与XGBOOST算法概述LightGBM: 用于排序,分类,回归等机器学习任务,支持高效率并行训练XGBOOST:来源于GBDTLightGBM与XGBOOST相关理论基础:Boosting集成学习的一个分支,通过将弱分类器训练成强分类器,达到准确分类的效果Gradient boostingBoosting分为Adaboost和Gradient Boosting,lightGBM属于gradient boosting? 什么是gradient boosting,与adabo

javascript鼠标双击时触发事件大全_鼠标双击事件_爱技术的大仙的博客-程序员宝宝

javascript事件列表解说事件 浏览器支持 解说一般事件 onclick IE3、N2 鼠标点击时触发此事件ondblclick IE4、N4 鼠标双击时触发此事件onmousedown IE4、N4 按下鼠标时触发此事件onmouseup IE4、N4 鼠标按下后松开鼠标时触发此事件onmouseover IE3、N2 当鼠标移动到某对象范围的上方时触发此事件onmousemove IE4、N4 鼠标移动时触发此事件onmouseout IE4、N3 当鼠标离开某对象范围时触发此事件

随便推点

unity打开网页_unity 打开网页_不朽之夜的博客-程序员宝宝

unity里面打开网页接触了一些,没做深入研究,仅以简单展示网页为主。分为两类,一是PC端打开网页,二是android端打开网页。网页插件或方案Unity之PC版,window。如果网页只是单独二维码图片,则采用www请求得到www.texture;Unity之PC版,window。如果网页不是单独一张二维码图片,有其他文字,样式,js等,使用插件WWebView(该插件开通了...

JEESZ分布式架构平台介绍_Summer之夏的博客-程序员宝宝

1.   项目核心代码结构截图      jeesz-utils            jeesz-config            jeesz-framework             jeesz-core-cms              jeesz-core-gen              jeesz-core-bookmark  

华为机试(一):字符串最后一个单词的长度_qq_42602999的博客-程序员宝宝

题目描述计算字符串最后一个单词的长度,单词以空格隔开。输入描述一行字符串,非空,长度小于5000。输出描述整数N,最后一个单词的长度。示例输入:hello world输出:5思路利用 getline() 函数读取整行字符串,从字符串末尾开始,向前计数,遇到空格停止。#include &lt;iostream&gt;#include &lt;string&gt;usin...

CRT显示器_kayyyuan的博客-程序员宝宝

CRT显示器简介 CRT显示器学名为“阴极射线显像管”,是一种使用阴极射线管(Cathode Ray Tube)的显示器。主要有五部分组成:电子枪(Electron Gun)、偏转线圈(Deflection coils)、荫罩(Shadow mask)、高压石墨电极和荧光粉涂层(Phosphor)及玻璃外壳。它是应用最广泛的显示器之一,CRT纯平显示器具有可视角度大、无坏点、色彩还原度高、色度均

委托的作用和性能讨论delegate_narlon的博客-程序员宝宝

委托绝对算是C#中一个非常重要和新颖的概念。可以直观的理解成一个安全的函数指针。delegate实际是如何工作的呢?delegate int MyDelegate(int x, int y);//生成自动化的类代码如下sealed class MyDelegate : System.MulticastDelegate{ public int Invoke(int x,i...

H5 缓存机制浅析 移动端 Web 加载性能优化_weixin_34232744的博客-程序员宝宝

为什么80%的码农都做不了架构师?&gt;&gt;&gt; ...