# @@@LINK检查工具

**URL:** <https://forum.freemdict.com/t/topic/45414>\
**Category:** 技术交流与词典编修\
**Tags:** 工具\
**Created:** [2026 年10 月 29 日 11:45 UTC](https://forum.freemdict.com/t/topic/45414 "2026-10-29T11:45:05Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

作者： ![feiwu](https://forumcdn.freemdict.com/letter_avatar_proxy/v4/letter/f/f17d59/32.png) [@feiwu](https://forum.freemdict.com/u/feiwu)\
发布日期： [2026 年10 月 29 日 11:45 UTC](https://forum.freemdict.com/t/topic/45414/1 "2026-10-29T11:45:05Z")

</div>

[@@@LINK检查.py](https://forum.freemdict.com/uploads/short-url/drS96ld9dk4CaDtW9GO3zGEqXx9.py) (5.4 KB)  
没有python环境的下载下面的zip，exe双击运行  
[@@@LINK检查.zip](https://forum.freemdict.com/uploads/short-url/qbsxJ208qddqAzmu0ryKAKP8vKk.zip) (6.8 MB)

运行后拖入txt文件，检查`@@@LINK`的重复条目和循环引用。

如果有重复条目或循环引用会在txt文件夹内生成检查报告。

检查结果不构成修改建议。

---

<div class="post-metadata">

作者： ![feiwu](https://forumcdn.freemdict.com/letter_avatar_proxy/v4/letter/f/f17d59/32.png) [@feiwu](https://forum.freemdict.com/u/feiwu)\
发布日期： [2026 年10 月 29 日 11:54 UTC](https://forum.freemdict.com/t/topic/45414/2 "2026-10-29T11:54:31Z")

</div>

这种形式会被当作循环引用，但实际没有影响，因为它是这样跳的。

 ![image](https://forumcdn.freemdict.com/uploads/default/original/3X/9/f/9fc367861bb8c51063edbf5226cc500457d6fd57.png)

---

<div class="post-metadata">

作者： ![naisme](https://forumcdn.freemdict.com/letter_avatar_proxy/v4/letter/n/ac91a4/32.png) [@naisme](https://forum.freemdict.com/u/naisme)\
发布日期： [2026 年10 月 29 日 14:54 UTC](https://forum.freemdict.com/t/topic/45414/3 "2026-10-29T14:54:29Z")

</div>

先将txt做成一词条一行。将\n比如换成freemdictcom，提取@@@LINK=的行，做成input.txt。  
这样input.txt中循环的行可能如下：  
第100行的内容：ゲンナリfreemdictcom@@@LINK=げんなりfreemdictcom\</\>  
第200行的内容：げんなりfreemdictcom@@@LINK=ゲンナリfreemdictcom\</\>

目标是去掉类似第200行。

将下列代码保存为fix-links.js后，用法: node fix-links.js input.txt，output.txt就是处理好的（将第200行去掉）。几十M的文本，几秒就处理好了。

```auto
const fs = require('fs');

// 用法: node fix-links.js input.txt output.txt
const inputFile = process.argv[2];
const outputFile = process.argv[3] || 'output.txt';

if (!inputFile) {
  console.error('用法: node fix-links.js input.txt [output.txt]');
  process.exit(1);
}

// 1. 读取（自动识别 BOM/UTF-8；如文件是 UTF-16 请见下方说明）
let text = fs.readFileSync(inputFile, 'utf8');
text = text.replace(/^\uFEFF/, ''); // 去掉 BOM

// 2. 按行拆分（兼容 \r\n 和 \n）
const lines = text.split(/\r?\n/);

// 3. 解析每行
const parsed = []; // 与 lines 等长，元素为 null 或 {key, val}
const normSet = new Set(); // 规范化后的 "KEY@@@LINK=VALUE"

for (const line of lines) {
  // 去掉行尾 </> 和空白
  const core = line.replace(/<\/>\s*$/, '');
  const m = core.match(/^(.+?)@@@LINK=(.+)$/);
  if (m) {
    const key = m[1];
    const val = m[2];
    parsed.push({ key, val });
    normSet.add(`${key}@@@LINK=${val}`);
  } else {
    parsed.push(null);
  }
}

// 4. 决定保留哪些行
const keep = [];
for (let i = 0; i < lines.length; i++) {
  const p = parsed[i];
  if (!p) {
    keep.push(lines[i]); // 非 LINK 行原样保留
    continue;
  }
  const reverse = `${p.val}@@@LINK=${p.key}`;
  if (!normSet.has(reverse)) {
    keep.push(lines[i]); // 没有相互指引 → 保留
    continue;
  }
  // 有相互指引：判断 KEY 首字符是否片假名 U+30A1–U+30FE
  const c = p.key.codePointAt(0);
  if (c >= 0x30A1 && c <= 0x30FE) {
    keep.push(lines[i]); // 片假名 KEY → 保留
  }
  // 平假名 KEY → 删除
}

// 5. 写回（保留原换行风格）
const newline = text.includes('\r\n') ? '\r\n' : '\n';
fs.writeFileSync(outputFile, keep.join(newline), 'utf8');

console.log(`完成：读入 ${lines.length} 行，保留 ${keep.length} 行 → ${outputFile}`);

```
