iT邦幫忙

2026 iThome 鐵人賽

DAY 2
0
佛心分享-SideProject30

打造 APR Engineer 的生產力平台,從 Flow Tracer 到 SignOff DashBoard 的落地實戰系列 第 2

【Day 02】突破無 SSH 與 FTP 的環境:一段關於「五鬼運財」的故事

  • 分享至 

  • xImage
  •  

前言

在這個系列要開始真正介紹未來的工具之前,先來解決一個大哉問,開發環境該怎麼搞 ?

畢竟巧婦難為無米之炊嘛

因為對許多半導體或大企業的工程師來說,平常是用遠端工作站的方式工作的,諸如:

  • Exceed
  • DSV
  • VDI

如果是在自由度較高的 Linux 環境,遇到想傳檔案的情況通常都不會有什麼困難,不過就一個指令的事情罷了,但現實是許多公司都有嚴格的資安考量,通常都是不開放 SSH,也不提供 FTP Server 的,任何軟體安裝都需要通過 IT 審核或資安單位的核准,可能你只是想安裝一個 numpy 套件或 node.js,都需要跑完一整套流程跟簽呈 😵‍💫 甚至有可能你跑完流程後還不見得能裝。

在這看似絕望、寸步難行的環境裡,腦海中忽然閃過了一個靈感:

https://ithelp.ithome.com.tw/upload/images/20260805/20127932wVkmG4kjVG.jpg

還可以 Crtl + C 跟 Crtl + V 呢!

那如果我把檔案轉都成像 binary code 那樣呢 0101 那種感覺?貼進去工作站後我再編譯出來

檔案 > 轉碼 > 複製貼上  > 解碼

豈不是能完美繞過檔案傳輸的限制

事實上,還真的就是這樣做的!這個技術原理其實非常古老且成熟,我們用的轉碼機制就是 Base64 Encoding

Base64 是什麼

Base64 是一種 Binary-to-Text 的編碼方法,能將任意的 Raw Bytes(如二進位檔、圖片、壓縮檔)轉換成由 ASCII 友善字元組成的純文字序列。

例如一個壓縮檔 tool.tar.gz,經過編碼後會變成一串字串:

H4sIAAAAAAAAA...

對系統而言,這只是一段普通文字;但實際上,裡面完整藏著二進位檔案的原始數據。

實務上最大的挑戰

雖然理論非常簡單,但真正的痛點在於:

剪貼簿並不是無限大的。甚至可以說有點小

這個容量限制通常不是來自 Linux 核心或 Vim 本身,而是源於遠端桌面協定、X11、DSV、Terminal Emulator 或 Clipboard Redirection 的緩衝區機制。因此,在不同公司的系統環境下,能承受的字串長度差異極大。

根據個人的經驗,有成功透過剪貼簿一次搬運過近 20 MB 的文字資料;但這並不代表所有環境都能如此幸運。

比較穩妥的做法是:

  • 大檔切小塊(Chunking)
  • 逐段搬運

不要挑戰一次把幾百 MB 的字串直接複製起來

檔案切塊與 Base64 編碼工具

這裡分享一個我寫的 Python 自動化腳本,它能自動完成以下流程:

  1. 計算原始檔案的 SHA256 Hash(用於後續校驗,但我都偷懶沒驗)
  2. 切割檔案為指定的份數
  3. 進行 Base64 編碼並自動換行(防止爆剪貼簿限制)
  4. 產生記錄後續還原資訊的 Manifest 檔
  5. 直接將指定的 Part 複製到系統剪貼簿(Windows 適用)
import base64
import hashlib
import math
import os
import subprocess
import sys


def sha256_file(path):
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for block in iter(lambda: f.read(1024 * 1024), b""):
            h.update(block)
    return h.hexdigest()


def copy_to_clipboard(text):
    try:
        process = subprocess.Popen(["clip.exe"], stdin=subprocess.PIPE, text=True)
        process.communicate(input=text)
        return process.returncode == 0
    except Exception:
        return False


def main():
    if len(sys.argv) < 2:
        print("Usage: python make_b64_parts.py <file> [num_parts] [copy_part_no]")
        print("Example: python make_b64_parts.py winflow_offline_py311.tar.gz 8")
        print("Example: python make_b64_parts.py winflow_offline_py311.tar.gz 8 1")
        sys.exit(1)

    file_path = sys.argv[1]
    num_parts = int(sys.argv[2]) if len(sys.argv) >= 3 else 8
    copy_part_no = int(sys.argv[3]) if len(sys.argv) >= 4 else None

    if not os.path.exists(file_path):
        print(f"ERROR: 找不到檔案: {file_path}", file=sys.stderr)
        sys.exit(1)

    file_size = os.path.getsize(file_path)
    chunk_size = math.ceil(file_size / num_parts)
    digest = sha256_file(file_path)

    base_name = os.path.basename(file_path)
    out_dir = f"{base_name}.b64parts"
    os.makedirs(out_dir, exist_ok=True)

    print(f"Input file: {file_path}", file=sys.stderr)
    print(f"Original size: {file_size} bytes", file=sys.stderr)
    print(f"SHA256: {digest}", file=sys.stderr)
    print(f"Parts: {num_parts}", file=sys.stderr)
    print(f"Chunk size: {chunk_size} bytes", file=sys.stderr)
    print(f"Output dir: {out_dir}", file=sys.stderr)

    manifest_path = os.path.join(out_dir, "manifest.txt")

    with open(file_path, "rb") as src, open(manifest_path, "w", encoding="utf-8") as manifest:
        manifest.write(f"filename={base_name}\n")
        manifest.write(f"size={file_size}\n")
        manifest.write(f"sha256={digest}\n")
        manifest.write(f"parts={num_parts}\n")

        for i in range(1, num_parts + 1):
            raw = src.read(chunk_size)
            if not raw:
                break

            b64 = base64.b64encode(raw).decode("ascii")
            part_name = f"part_{i:02d}_of_{num_parts:02d}.b64"
            part_path = os.path.join(out_dir, part_name)

            with open(part_path, "w", encoding="ascii") as f:
                # 每 76 字元換行,方便貼上,也比較不容易爆 terminal
                for j in range(0, len(b64), 76):
                    f.write(b64[j:j + 76] + "\n")

            manifest.write(f"{part_name} raw_size={len(raw)} b64_size={len(b64)}\n")

            print(
                f"Created {part_name}: raw={len(raw)} bytes, b64={len(b64)} chars",
                file=sys.stderr,
            )

    print("\nDONE.", file=sys.stderr)
    print(f"Manifest: {manifest_path}", file=sys.stderr)

    if copy_part_no is not None:
        part_name = f"part_{copy_part_no:02d}_of_{num_parts:02d}.b64"
        part_path = os.path.join(out_dir, part_name)

        if not os.path.exists(part_path):
            print(f"ERROR: part not found: {part_path}", file=sys.stderr)
            sys.exit(1)

        with open(part_path, "r", encoding="ascii") as f:
            text = f.read()

        ok = copy_to_clipboard(text)
        if ok:
            print(f"Copied {part_name} to clipboard.", file=sys.stderr)
        else:
            print(f"Failed to copy {part_name} to clipboard.", file=sys.stderr)


if __name__ == "__main__":
    main()

使用方式:

假設你今天打包了一份壓縮檔叫 my_tool.tar.gz

python make_b64_parts.py my_tool.tar.gz [你想切幾份] [直接複製哪一段進剪貼簿]
# 因為我都傳不怎麼大的東西,所以都這樣用
python make_b64_parts.py my_tool.tar.gz 1 1

輸出 (假設我們切 8 份):

my_tool.tar.gz.b64parts/
    ├── manifest.txt
    ├── part_01_of_08.b64
    ├── part_02_of_08.b64
    ├── ...
    └── part_08_of_08.b64

要切成幾份比較合理?

評估切片數量時,必須注意 Base64 的膨脹特性:

Base64 將每 3 Bytes 的 Binary 資料轉化為 4 個 ASCII 字元,若末端不足則用 = 補齊。這意味著轉換後的文字體積會膨脹約 33%(例如:20 MB 的檔案編碼後會變成大約 26.6 MB)。

假設預估遠端終端機的 Clipboard 安全上限為 5 MB,那麼一個 20 MB 的原始檔案經 Base64 膨脹後約 26.6 MB,建議至少切割為 6~7 份以上才會比較穩妥。

接收端(遠端工作站)如何還原?

當你把所有的 .b64 檔案都逐一貼上並儲存到工作站後,還原只需兩行指令:

cat part_* > all.b64
# 解碼
base64 -d all.b64 > my_tool.tar.gz

好耶搬進來囉 ~ 🎉🎉🎉

免責聲明

本文內容僅供技術研究、教育與工作效率改善之用途,目的在於介紹 Base64 編碼、檔案切割與資料重組的技術原理。
實際操作前,請務必確認符合公司資訊安全政策、軟體使用規範及相關管理要求。本文不鼓勵也不建議繞過企業資安機制、傳輸機密資料或安裝未經授權之軟體。任何實際應用行為所產生之風險、責任與合規問題,均應由使用者自行評估與承擔。


上一篇
【DAY 1】 APR 工程師在幹嘛 ? 工作上的痛點在哪 ?
系列文
打造 APR Engineer 的生產力平台,從 Flow Tracer 到 SignOff DashBoard 的落地實戰2
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言