Install
openclaw skills install feishu-doc-copy完整复制飞书云文档至同一账号云空间,分块读取与写入,保持文字、表格、代码格式100%一致(不含图片)。
openclaw skills install feishu-doc-copy功能:完整复制飞书云文档到同一账号的云空间,保持文字、表格、代码、格式 100% 一致(图片除外)。
适用场景:
前提条件:
限制:
分块读取 + 分块写入 + 严格验证
这是唯一可行的路径,因为:
# 读取源文档前 50 字符,获取 total_length
openclaw feishu fetch-doc --doc_id <SOURCE_DOC_ID> --limit 50
返回值示例:
{
"doc_id": "ABC123xyz",
"title": "文档标题",
"total_length": 43548,
"has_more": true,
"length": 50,
"markdown": "..."
}
关键信息:
total_length:原文档总字符数(用于计算分块数量)doc_id:源文档 ID# 每块大小:9000 字符(推荐值,避免切断表格/代码块)
BLOCK_SIZE=9000
# 计算分块数量
BLOCK_COUNT = ceil(TOTAL_LENGTH / BLOCK_SIZE)
示例:
分块偏移量:
块 0: offset 0
块 1: offset 9000
块 2: offset 18000
块 3: offset 27000
块 4: offset 36000
# 创建临时目录存储分块文件
TEMP_DIR="/tmp/feishu_copy_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$TEMP_DIR"
循环读取每一块:
OFFSET=0
BLOCK_NUM=0
while [ $OFFSET -lt $TOTAL_LENGTH ]; do
echo "读取块 $BLOCK_NUM(offset: $OFFSET)..."
# 读取当前块
openclaw feishu fetch-doc \
--doc_id "$SOURCE_DOC_ID" \
--limit $BLOCK_SIZE \
--offset $OFFSET \
> "$TEMP_DIR/chunk_${BLOCK_NUM}.json"
# 提取 markdown 内容
cat "$TEMP_DIR/chunk_${BLOCK_NUM}.json" | jq -r '.markdown' > "$TEMP_DIR/chunk_${BLOCK_NUM}.md"
# 检查是否还有更多
HAS_MORE=$(cat "$TEMP_DIR/chunk_${BLOCK_NUM}.json" | jq -r '.has_more')
if [ "$HAS_MORE" = "false" ]; then
echo "最后一块读取完成"
break
fi
OFFSET=$(( $OFFSET + $BLOCK_SIZE ))
BLOCK_NUM=$(( $BLOCK_NUM + 1 ))
# 避免 API 限流
sleep 0.5
done
关键验证点:
has_more 标志has_more 必须为 false# 创建新文档(初始内容可以是任意占位符)
openclaw feishu create-doc \
--title "<新文档标题>" \
--markdown "# <新文档标题>\n\n正在搬运中..."
返回值示例:
{
"doc_id": "XYZ789abc",
"doc_url": "https://www.feishu.cn/docx/XYZ789abc",
"message": "文档创建成功"
}
记录新文档 ID:NEW_DOC_ID
写入规则:
overwrite 模式append 模式TOTAL_BLOCKS=$BLOCK_NUM # 块编号从 0 开始,所以总块数 = 最大块编号 + 1
for i in $(seq 0 $BLOCK_NUM); do
CHUNK_FILE="$TEMP_DIR/chunk_${i}.md"
if [ $i -eq 0 ]; then
MODE="overwrite"
echo "写入块 $i($MODE 模式)..."
else
MODE="append"
echo "追加块 $i($MODE 模式)..."
fi
# 写入当前块
openclaw feishu update-doc \
--doc_id "$NEW_DOC_ID" \
--mode $MODE \
--markdown @"$CHUNK_FILE"
# 避免 API 限流
sleep 1
done
关键验证点:
overwriteappend# 获取新文档信息
openclaw feishu fetch-doc \
--doc_id "$NEW_DOC_ID" \
--limit 50 \
> "$TEMP_DIR/new_doc_info.json"
# 提取新文档总长度
NEW_LENGTH=$(cat "$TEMP_DIR/new_doc_info.json" | jq -r '.total_length')
# 计算差异
DIFF=$(( $TOTAL_LENGTH - $NEW_LENGTH ))
PERCENT=$(echo "scale=2; $NEW_LENGTH * 100 / $TOTAL_LENGTH" | bc)
echo "原文档:$TOTAL_LENGTH 字符"
echo "新文档:$NEW_LENGTH 字符"
echo "差异:$DIFF 字符($PERCENT% 完整)"
# 验证通过条件:差异 < 2%
if [ $(echo "$PERCENT < 98" | bc) -eq 1 ]; then
echo "⚠️ 差异较大,请手动检查"
else
echo "✅ 验证通过"
fi
预期结果:
rm -rf "$TEMP_DIR"
将以上流程整合为可执行脚本:
#!/bin/bash
# 飞书文档完整复制脚本
# 用法:./feishu-doc-copy.sh <源文档 ID> <新文档标题>
set -e # 遇到错误立即退出
SOURCE_DOC_ID="$1"
NEW_DOC_TITLE="$2"
BLOCK_SIZE=9000 # 每块字符数
if [ -z "$SOURCE_DOC_ID" ] || [ -z "$NEW_DOC_TITLE" ]; then
echo "用法:$0 <源文档 ID> <新文档标题>"
echo "示例:$0 ABC123xyz 文档标题 - 副本"
exit 1
fi
# 创建临时目录
TEMP_DIR="/tmp/feishu_copy_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$TEMP_DIR"
echo "📋 开始复制飞书文档..."
echo " 源文档 ID: $SOURCE_DOC_ID"
echo " 新文档标题:$NEW_DOC_TITLE"
echo " 临时目录:$TEMP_DIR"
echo ""
# 步骤 1: 获取源文档信息
echo "📊 步骤 1: 获取源文档信息..."
DOC_INFO=$(openclaw feishu fetch-doc --doc_id "$SOURCE_DOC_ID" --limit 50)
TOTAL_LENGTH=$(echo "$DOC_INFO" | jq -r '.total_length')
SOURCE_TITLE=$(echo "$DOC_INFO" | jq -r '.title')
echo " 源文档标题:$SOURCE_TITLE"
echo " 原文档总长度:$TOTAL_LENGTH 字符"
# 步骤 2: 计算分块数量
BLOCK_COUNT=$(( ($TOTAL_LENGTH + $BLOCK_SIZE - 1) / $BLOCK_SIZE ))
echo " 分块大小:$BLOCK_SIZE 字符/块"
echo " 分块数量:$BLOCK_COUNT 块"
echo ""
# 步骤 3: 分块读取
echo "📥 步骤 3: 分块读取源文档..."
OFFSET=0
BLOCK_NUM=0
while [ $OFFSET -lt $TOTAL_LENGTH ]; do
echo " 读取块 $BLOCK_NUM(offset: $OFFSET)..."
openclaw feishu fetch-doc \
--doc_id "$SOURCE_DOC_ID" \
--limit $BLOCK_SIZE \
--offset $OFFSET \
> "$TEMP_DIR/chunk_${BLOCK_NUM}.json"
# 提取 markdown 内容
cat "$TEMP_DIR/chunk_${BLOCK_NUM}.json" | jq -r '.markdown' > "$TEMP_DIR/chunk_${BLOCK_NUM}.md"
# 检查是否还有更多
HAS_MORE=$(cat "$TEMP_DIR/chunk_${BLOCK_NUM}.json" | jq -r '.has_more')
if [ "$HAS_MORE" = "false" ]; then
echo " ✅ 最后一块读取完成"
break
fi
OFFSET=$(( $OFFSET + $BLOCK_SIZE ))
BLOCK_NUM=$(( $BLOCK_NUM + 1 ))
sleep 0.5
done
TOTAL_BLOCKS=$BLOCK_NUM
echo " ✅ 共读取 $(( $TOTAL_BLOCKS + 1 )) 块"
echo ""
# 步骤 4: 创建新文档
echo "📝 步骤 4: 创建新文档..."
NEW_DOC=$(openclaw feishu create-doc \
--title "$NEW_DOC_TITLE" \
--markdown "# $NEW_DOC_TITLE\n\n正在搬运中...")
NEW_DOC_ID=$(echo "$NEW_DOC" | jq -r '.doc_id')
NEW_DOC_URL=$(echo "$NEW_DOC" | jq -r '.doc_url')
echo " 新文档 ID: $NEW_DOC_ID"
echo " 新文档链接:$NEW_DOC_URL"
echo ""
# 步骤 5: 分块写入
echo "📤 步骤 5: 分块写入新文档..."
for i in $(seq 0 $TOTAL_BLOCKS); do
CHUNK_FILE="$TEMP_DIR/chunk_${i}.md"
if [ $i -eq 0 ]; then
MODE="overwrite"
else
MODE="append"
fi
echo " 写入块 $i($MODE 模式)..."
openclaw feishu update-doc \
--doc_id "$NEW_DOC_ID" \
--mode $MODE \
--markdown @"$CHUNK_FILE"
sleep 1
done
echo " ✅ 所有块写入完成"
echo ""
# 步骤 6: 验证
echo "🔍 步骤 6: 验证结果..."
NEW_DOC_INFO=$(openclaw feishu fetch-doc --doc_id "$NEW_DOC_ID" --limit 50)
NEW_LENGTH=$(echo "$NEW_DOC_INFO" | jq -r '.total_length')
DIFF=$(( $TOTAL_LENGTH - $NEW_LENGTH ))
PERCENT=$(echo "scale=2; $NEW_LENGTH * 100 / $TOTAL_LENGTH" | bc)
echo " 原文档:$TOTAL_LENGTH 字符"
echo " 新文档:$NEW_LENGTH 字符"
echo " 差异:$DIFF 字符($PERCENT% 完整)"
if [ $(echo "$PERCENT >= 98" | bc) -eq 1 ]; then
echo " ✅ 验证通过(差异 < 2%,主要是图片 token)"
VERIFICATION_STATUS="✅ 通过"
else
echo " ⚠️ 差异较大,请手动检查"
VERIFICATION_STATUS="⚠️ 需检查"
fi
echo ""
# 步骤 7: 清理
echo "🧹 步骤 7: 清理临时文件..."
rm -rf "$TEMP_DIR"
echo " ✅ 临时文件已清理"
echo ""
# 输出结果
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🎉 复制完成!"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 源文档:$SOURCE_TITLE"
echo " 新文档:$NEW_DOC_TITLE"
echo " 新文档链接:$NEW_DOC_URL"
echo " 完整性:$VERIFICATION_STATUS"
echo " 原文档:$TOTAL_LENGTH 字符"
echo " 新文档:$NEW_LENGTH 字符"
echo " 差异:$DIFF 字符"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# 1. 保存脚本
# 将上面的完整脚本保存为 ~/.openclaw/scripts/feishu-doc-copy.sh
# 2. 添加执行权限
chmod +x ~/.openclaw/scripts/feishu-doc-copy.sh
# 3. 执行
~/.openclaw/scripts/feishu-doc-copy.sh <源文档 ID> <新文档标题>
# 示例
~/.openclaw/scripts/feishu-doc-copy.sh ABC123xyz 文档标题 - 副本
# 1. 获取源文档信息
openclaw feishu fetch-doc --doc_id <SOURCE_DOC_ID> --limit 50
# 2. 创建临时目录
mkdir -p /tmp/feishu_copy_$$
# 3. 分块读取(假设原文档 43548 字符)
openclaw feishu fetch-doc --doc_id <SOURCE_DOC_ID> --limit 9000 --offset 0 > /tmp/feishu_copy_$$/chunk_0.json
openclaw feishu fetch-doc --doc_id <SOURCE_DOC_ID> --limit 9000 --offset 9000 > /tmp/feishu_copy_$$/chunk_1.json
openclaw feishu fetch-doc --doc_id <SOURCE_DOC_ID> --limit 9000 --offset 18000 > /tmp/feishu_copy_$$/chunk_2.json
openclaw feishu fetch-doc --doc_id <SOURCE_DOC_ID> --limit 9000 --offset 27000 > /tmp/feishu_copy_$$/chunk_3.json
openclaw feishu fetch-doc --doc_id <SOURCE_DOC_ID> --limit 9000 --offset 36000 > /tmp/feishu_copy_$$/chunk_4.json
# 4. 提取 markdown
for f in /tmp/feishu_copy_$$/chunk_*.json; do
cat "$f" | jq -r '.markdown' > "${f%.json}.md"
done
# 5. 创建新文档
openclaw feishu create-doc --title "<新文档标题>" --markdown "# <新文档标题>\n\n正在搬运中..."
# 6. 分块写入
openclaw feishu update-doc --doc_id <NEW_DOC_ID> --mode overwrite --markdown @/tmp/feishu_copy_$$/chunk_0.md
openclaw feishu update-doc --doc_id <NEW_DOC_ID> --mode append --markdown @/tmp/feishu_copy_$$/chunk_1.md
openclaw feishu update-doc --doc_id <NEW_DOC_ID> --mode append --markdown @/tmp/feishu_copy_$$/chunk_2.md
openclaw feishu update-doc --doc_id <NEW_DOC_ID> --mode append --markdown @/tmp/feishu_copy_$$/chunk_3.md
openclaw feishu update-doc --doc_id <NEW_DOC_ID> --mode append --markdown @/tmp/feishu_copy_$$/chunk_4.md
# 7. 验证
openclaw feishu fetch-doc --doc_id <NEW_DOC_ID> --limit 50
复制完成后,请检查以下项目:
<lark-table> 标签完整A: 正常现象,主要原因:
只要差异 < 2%,文档内容就是完整的。
A: 检查 <lark-table> 标签是否完整:
rows、cols 属性<lark-tr> 和 <lark-td> 标签必须配对A: 从飞书文档 URL 中提取:
URL: https://xxx.feishu.cn/wiki/DOC_ID_HERE
↑
文档 ID
文档 ID 是 URL 最后一段字符串。
A: 排查步骤:
openclaw feishu 命令是否可用A: 如果写入中途失败:
| 分块大小 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|
| 5,000 | 不易切断表格/代码 | API 调用次数多 | 复杂文档 |
| 9,000 | 平衡点 | - | 推荐 |
| 15,000 | API 调用次数少 | 可能超过限制 | 简单文档 |
脚本使用 set -e,遇到错误立即退出。临时文件保留,便于调试。
MIT License
本技能基于 OpenClaw 飞书 API 实现,适用于所有有查看权限但无复制权限的飞书文档。