命令列、GitHub
與正規式
動手做在自己的電腦上完成的練習。頁面練習在本頁的模擬工具中練習,不會影響電腦上的檔案。
終端機
開啟終端機
終端機(terminal)是以文字指令操作電腦的程式。與以滑鼠操作的圖形介面(GUI)相比,命令列有下列優點:
- 可以重現:指令可以記錄下來,之後或在另一台電腦上重新執行,得到相同的結果;滑鼠操作則難以準確描述與重複。
- 可以大量處理:一行指令就能處理許多檔案,例如將資料夾中所有
.csv檔一次複製到別處。 - 可以自動執行:多行指令可以寫成腳本(script),交由電腦依序執行。
- 許多工具只有命令列介面:Git、Python 套件的安裝,以及連線到遠端伺服器(例如實驗室的 GPU 主機)時,通常只能透過終端機操作。
點選「開始」,搜尋 PowerShell,開啟「Windows PowerShell」。以下所有指令都在這個視窗中輸入。
按 ⌘ + 空白鍵,輸入 Terminal(終端機)並按 Enter。以下所有指令都在這個視窗中輸入。
開始安裝 Git、GitHub CLI 與 VS Code
這些工具要到「Git 與 GitHub 儲存庫」才會用到,各自的用途也在該節說明。由於下載需要一段時間,先在這裡開始安裝,安裝期間繼續進行下一節。
完成後,電腦上應有下列軟體:
| 軟體 | 用途 | 取得方式 |
|---|---|---|
| 終端機 | 以文字指令操作電腦 | Windows PowerShell,系統內建Terminal,系統內建 |
| Git | 記錄檔案的修改歷史 | 下方安裝 |
GitHub CLI(gh) | 在終端機中登入 GitHub | 下方安裝 |
| Visual Studio Code | 編輯文字檔與程式;之後的課程也會使用 | 下方安裝 |
| 瀏覽器 | 使用 GitHub 與 RegExr | 任一種皆可 |
逐行貼上下列指令並按 Enter。終端機中出現 [Y] Yes [N] No 時,輸入 Y 再按 Enter;跳出權限詢問視窗時,選擇「是」。每一行都顯示 Successfully installed 即完成。
winget install --id Git.Git -e --source wingetwinget install --id GitHub.cli -e --source wingetwinget install --id Microsoft.VisualStudioCode -e --source wingetwinget 執行期間,這個視窗無法輸入其他指令,可以從「開始」再開一個 PowerShell 視窗繼續下一節。安裝完成後,須關閉並重新開啟 PowerShell,新指令才能使用。
無法使用 winget 時
改為下載安裝檔:Git 至 git-scm.com,GitHub CLI 至 cli.github.com 下載 .msi,VS Code 至 code.visualstudio.com 下載。安裝選項皆使用預設值即可。
先安裝 Git。貼上下列指令並按 Enter,在跳出的視窗中點選「安裝」:
xcode-select --install若顯示 xcode-select: error: command line tools are already installed,表示已經安裝(訊息中雖有 error,但並非錯誤),可直接進行下一步。安裝會在另一個視窗進行,終端機可以繼續使用。
接著安裝 GitHub CLI:開啟 GitHub CLI 下載頁,在頁面下方的 Assets 清單中,點選檔名結尾為 _macOS_universal.pkg 的檔案,下載後點兩下安裝。universal 版同時支援 Intel 與 Apple 晶片(M 系列)的 Mac,不需另外區分。已安裝 Homebrew 者也可以執行 brew install gh。
最後安裝 VS Code:至 code.visualstudio.com 下載 macOS 的 Universal 版本(同時支援 Intel 與 Apple 晶片),開啟下載的檔案後,將 Visual Studio Code 拖曳到「應用程式」資料夾。開啟 VS Code,按 ⇧⌘P,輸入並執行 Shell Command: Install 'code' command in PATH,終端機才能使用 code 指令。
檔案與資料夾
電腦中的檔案存放在資料夾裡。資料夾可以包含檔案,也可以包含其他資料夾,因此整體呈樹狀結構。下載 week04-lab.zip 後,將它解壓縮到「下載」資料夾:
- 在檔案總管中對
week04-lab.zip按右鍵,選擇「解壓縮全部」,確認目的地路徑的結尾是Downloads\week04-lab,再按「解壓縮」。直接按兩下 zip 檔只會瀏覽內容,不會解壓縮。 - 在 Finder 中對
week04-lab.zip按兩下即可。Safari 下載時通常已自動解壓縮。
完成後,「下載」資料夾中應有一個 week04-lab 資料夾。若下載了兩次,資料夾名稱可能變成 week04-lab (1),請刪除多餘的一個,或將它改名為 week04-lab。資料夾的結構如下:
week04-lab/
├── README.txt
├── data/
│ ├── results_2025.csv
│ └── results_2026.csv
└── notes/
└── week1.txt
圖中每縮排一層,表示該項目位於上一層資料夾之內。例如 data 與 notes 位於 week04-lab 之內,results_2025.csv 位於 data 之內。名稱以 / 結尾的是資料夾,其餘是檔案。
在資料夾之間移動
終端機在任何時刻都位於某一個資料夾中,這個資料夾稱為工作目錄(working directory)。pwd 顯示目前的工作目錄,ls 列出其中的內容,cd 切換到另一個資料夾。
進入練習資料夾
cd ~\Downloads\week04-labcd ~/Downloads/week04-lab~ 代表使用者的家目錄(home directory),也就是以帳號命名的資料夾:C:\Users\帳號名稱/Users/帳號名稱。因此不論帳號名稱為何,這行指令都適用。
執行成功時,提示字元的結尾會變成 week04-lab>week04-lab %。接著執行 ls,應列出 README.txt、data、notes 三項。若出現 Cannot find pathno such file or directory,表示資料夾不在「下載」中或名稱不同,請回到「檔案與資料夾」檢查解壓縮的結果。
第一次在「下載」資料夾中執行指令時,macOS 可能詢問「終端機」是否可取用「下載項目」檔案夾,請選擇「好」。若已選擇不允許,執行指令時會出現 Operation not permitted;可到「系統設定 → 隱私權與安全性 → 檔案與檔案夾 → 終端機」開啟「下載項目」。
若 week04-lab 不在「下載」資料夾中,可先輸入 cd 與一個空白,再將 week04-lab 資料夾從檔案總管 Finder 拖曳到終端機視窗,路徑會自動填入,然後按 Enter。
絕對路徑與相對路徑
路徑(path)是描述檔案或資料夾位置的寫法,分為兩種:
- 絕對路徑(absolute path)從最上層開始寫起,不論目前的工作目錄在哪裡,都指向同一個位置,例如
C:\Users\帳號名稱\Downloads\week04-lab\data。Windows 的絕對路徑以磁碟代號(如C:\)開頭/Users/帳號名稱/Downloads/week04-lab/data。macOS 與 Linux 的絕對路徑以/開頭。pwd輸出的就是絕對路徑。 - 相對路徑(relative path)從目前的工作目錄開始寫起,因此同一個相對路徑在不同的位置會指向不同的地方。
data、practice/與../notes都是相對路徑;.代表目前的資料夾,..代表上一層資料夾。
以 ~ 開頭的路徑以家目錄為起點,因此與絕對路徑相同,在任何位置都指向同一處。下表比較同一個指令在不同位置執行的結果:
| 目前位置 | 指令 | 結果 |
|---|---|---|
week04-lab | cd data | 進入 week04-lab 中的 data |
notes | cd data | 錯誤:notes 中沒有 data |
notes | cd ../data | 先回到 week04-lab,再進入 data |
| 任何位置 | cd ~\Downloads\week04-lab\datacd ~/Downloads/week04-lab/data | 進入 data |
下方的練習用終端機也接受絕對路徑(其中的帳號名稱固定為 you;在自己的電腦上請換成 pwd 顯示的帳號名稱),例如 cd C:\Users\you\Downloads\week04-lab\notescd /Users/you/Downloads/week04-lab/notes。
在自己的終端機執行 動手做
在自己的終端機中,從 week04-lab 開始依序執行下列指令,並對照上方的結果。之後的「練習:自行移動」會用到這裡建立的 practice。
pwdlscd datacd ..mkdir practicecp README.txt practice/cd practicecat README.txtmv README.txt README-copy.txtcd ..常用指令
下表列出本次使用的指令,範例皆以 week04-lab 為工作目錄。每一個指令都可以在上方的練習用終端機中嘗試。
| 指令 | 作用 | 範例 | 說明 |
|---|---|---|---|
pwd | 顯示目前的工作目錄 | pwd | 輸出絕對路徑;PowerShell 會先輸出 Path 標題。 |
ls | 列出資料夾的內容 | ls data | 不加名稱時列出目前的資料夾;也可以使用 dir。 |
cd | 切換工作目錄 | cd ../notes | cd .. 回到上一層。 |
mkdir | 建立資料夾 | mkdir practice | 名稱已存在時會出現錯誤。 |
cp | 複製檔案 | cp README.txt practice/ | 原檔保留不變。 |
mv | 移動或重新命名 | mv README.txt README-copy.txt | 目的地是資料夾時為移動,否則為重新命名。 |
cat | 顯示檔案內容 | cat README.txt | 適用於文字檔。 |
rm | 刪除檔案 | rm README-copy.txt | 刪除的檔案不會進入資源回收筒或垃圾桶,無法復原。 |
練習:自行移動 動手做
從練習用終端機左側的樹狀圖中任選一個資料夾,例如 notes 或 practice,在終端機中移動到該處,並以 pwd 與 ls 確認位置。接著嘗試只用一個 cd 指令抵達,例如由 data 執行 cd ../notes。
檢核:能說明分別從 data 與 practice 執行 cd .. 之後所在的位置。
Git 與 GitHub 儲存庫
Git 是一種版本控制系統(version control system)。它把資料夾中的修改記錄為一個個 commit(提交,即保存下來的版本),因此可以查看檔案的修改歷史、比較不同版本、回復先前的狀態,也能讓多人分別修改同一組檔案之後再加以合併。以「報告_最終版_v3.docx」這類檔名保存多個副本的做法,難以得知各版本之間的差異;Git 只保留一份檔案,修改的歷史則另外記錄。
b27a9c1Add exit task你的名字 · 10/7 12:58HEAD → maina90c6e8Resolve conflict in about-me.txt你的名字 · 10/7 12:315f4e3d2Update about-me.txt你的名字(在 GitHub 網頁上)· 10/7 12:208e5b0f7Update about-me你的名字 · 10/7 12:053c1d2a4Add about-me file你的名字 · 10/7 11:489f8e7d6Initial commit你的名字 · 10/7 11:30
command-line-lab 大致的歷史;git log 會列出同樣的內容。由 Git 管理的資料夾稱為儲存庫(repository,簡稱 repo)。Git 在本機運作,不需要網路。GitHub 則是存放儲存庫的網路服務,在遠端保留一份副本,用於備份、分享與協作;許多研究專案與開放原始碼軟體都在 GitHub 上公開程式與資料。本節在 GitHub 上建立一個儲存庫,並讓本機的 Git 取得存取權限。
1. 確認安裝
Git 與 GitHub CLI 已在「終端機」一節開始安裝。GitHub CLI(gh)用來在終端機中登入 GitHub;Visual Studio Code(簡稱 VS Code)是文字與程式編輯器,之後的課程也會使用。確認三者都已安裝完成:
git --versiongh --versioncode --version三行都顯示版本號即完成。若出現 is not recognizedcommand not found,表示尚未安裝完成,或終端機尚未重新開啟。
2. 以 VS Code 編輯檔案
本次實作一律從終端機開啟檔案,例如 code about-me.txt。若檔案不存在,VS Code 會開啟一個空白的分頁,按 Ctrl⌘ + S 存檔時,檔案就會建立在目前的工作目錄中,不必在存檔視窗中尋找位置。第一次開啟資料夾時,VS Code 可能詢問是否信任該資料夾的作者,選擇信任即可。
若 code 指令無法使用,可改用系統內建的編輯器:Windows 執行 notepad about-me.txt,記事本詢問是否建立新檔案時選擇「是」。macOS 先執行 touch about-me.txt 建立空白檔案,再以 open -e about-me.txt 用「文字編輯」開啟。
檔案總管預設隱藏副檔名,可在「檢視 → 顯示 → 副檔名」開啟顯示。
3. 註冊 GitHub 帳號
至 github.com/signup 註冊帳號,並開啟驗證信完成電子郵件驗證。未驗證的帳號可能無法建立儲存庫。
4. 建立儲存庫
- 登入 github.com,點選右上角的 + → New repository。
- 在 Repository name 輸入
command-line-lab。 - 選擇 Public,並開啟 Add README(部分版面為勾選 Add a README file)。
- 點選 Create repository。
5. 以 GitHub CLI 登入
gh auth login執行後,程式會依序提出四個問題(不同版本的用字可能略有差異)。第一、二、四題以方向鍵選擇,按 Enter 確認;第三題輸入 Y 再按 Enter:
| 問題 | 選項 |
|---|---|
| Where do you use GitHub? | GitHub.com |
| What is your preferred protocol for Git operations on this host? | HTTPS |
| Authenticate Git with your GitHub credentials? | 輸入 Y |
| How would you like to authenticate GitHub CLI? | Login with a web browser |
接著畫面會顯示一組一次性驗證碼(First copy your one-time code: XXXX-XXXX)。按 Enter 開啟瀏覽器,在頁面上輸入這組驗證碼並點選 Authorize;若瀏覽器沒有自動開啟,請手動前往 github.com/login/device。終端機顯示 Logged in as 帳號名稱,即表示登入完成。
gh auth status可隨時以此指令確認登入狀態。6. 設定使用者名稱與電子郵件
Git 會把這兩項資訊記錄在每一次 commit 中。每台電腦只需設定一次。將引號內的你的名字、你的信箱換成自己的資料後再執行:
git config --global user.name "你的名字"git config --global user.email "你的信箱"由於儲存庫是公開的,這個電子郵件會出現在 commit 紀錄中。若不希望公開,可到 github.com/settings/emails 取得 GitHub 提供的代用信箱(結尾為 users.noreply.github.com)。
提交與上傳修改
本節將 GitHub 上的儲存庫複製到本機,新增一個檔案,再讓這個修改依序經過暫存區與本機歷史,最後上傳到 GitHub。
1. 將儲存庫複製到本機(clone)
在儲存庫頁面點選綠色的 Code 按鈕,選擇 HTTPS,並點選右側的複製圖示。網址的格式為 https://github.com/帳號名稱/command-line-lab.git。
command-line-lab.git。git clone 會把 GitHub 上的儲存庫完整複製一份到本機,包含所有的修改紀錄。先回到「下載」資料夾,再輸入 git clone 與一個空白,然後貼上剛才複製的網址(在 PowerShell 中按 Ctrl + V 或按右鍵按 ⌘ + V):
cd ~\Downloadscd ~/Downloadsgit clone https://github.com/帳號名稱/command-line-lab.gitcd command-line-labgit status預期輸出包含 On branch main 與 nothing to commit, working tree clean。此時「下載」資料夾中有兩個資料夾:
Downloads/ ├── week04-lab/ 練習移動用的普通資料夾 └── command-line-lab/ 剛才 clone 的儲存庫
week04-lab 不是儲存庫;以下所有 Git 操作都在 command-line-lab 中進行。
為什麼分成四個區域
Git 把一次修改從編輯到分享分成三個步驟,每一步都由使用者決定:
git add:選擇哪些修改要納入下一次 commit。例如修改了三個檔案,但只有其中兩個屬於同一件事,就只 add 這兩個,第三個留到下一次。git commit:將暫存區的內容記錄為一個版本,之後可以隨時比較或回復。commit 只存在本機,不需要網路,因此可以先累積幾次 commit 再分享。git push:將本機的 commit 上傳到 GitHub,供他人檢視,也作為備份。上傳會影響共用同一個儲存庫的人,所以與 commit 分開進行。
另外兩個指令不會移動任何內容,只用來查看:git status 顯示每個檔案目前位於哪一個區域,是最常使用的指令;git log --oneline 列出本機歷史中的 commit。
四個區域因此分別對應:尚未決定的修改(工作資料夾)、準備記錄的修改(暫存區)、已經記錄的版本(本機歷史),以及已經分享的版本(GitHub)。
2. 新增 about-me.txt
在 command-line-lab 中,從終端機建立並開啟 about-me.txt:
code about-me.txt寫入一句話說明想研究的題目,存檔後關閉分頁。回到終端機執行 ls,應可看到 README.md 與 about-me.txt。
執行 code . 則會以 VS Code 開啟整個資料夾,左側會列出其中的檔案。
3. 暫存、提交與上傳
逐行執行下列指令。每一步的預期結果列在指令下方,可對照上方「Git 的四個區域」的模擬。
git statusabout-me.txt 列在 Untracked files 之下:修改位於工作資料夾。git add about-me.txt沒有輸出。git status改列在 Changes to be committed 之下:修改位於暫存區。git commit -m "Add about-me file"顯示類似 [main 3c1d2a4] Add about-me file 的訊息:修改已寫入本機歷史。git log --oneline最上方一行是剛才的 commit。git push最後一行為 main -> main:修改已上傳到 GitHub。重新整理 GitHub 上的儲存庫頁面,應可看到 about-me.txt。
4. 第二次修改 動手做
以 code about-me.txt 開啟檔案,修改內容並存檔,執行 git status 與 git diff 觀察輸出。git diff 會顯示尚未 add 的修改,例如:
@@ -1 +1 @@-我想研究台語的聲調。+我想研究台語和客語的聲調。
接著不參照上方的指令,依照四個區域的順序自行完成 add、commit 與 push。commit 訊息可以自訂,例如 "Update about-me"。完成後重新整理 GitHub 頁面,確認內容已更新。
5. 衝突 動手做
同一個檔案的同一處若在兩個地方分別被修改,Git 無法判斷應保留哪一個版本,這種情形稱為衝突(conflict)。多人協作時,衝突通常發生在兩人修改了同一段文字;一個人使用時,也可能因為在 GitHub 網頁與本機分別修改而發生。以下刻意製造一次衝突,再加以解決。
-
在 GitHub 的儲存庫頁面點選
about-me.txt,再點選右上角的鉛筆圖示(Edit this file)。修改句子後,點選 Commit changes,並在跳出的對話框中再按一次 Commit changes。頁面上顯示修改後的句子即完成。 -
不執行
git pull,直接在本機以編輯器把同一行改成另一個句子,存檔後執行:git add about-me.txtgit commit -m "Update about-me again"git pushGitHub 上有一個本機沒有的 commit,因此 push 會被拒絕:
! [rejected] main -> main (fetch first) error: failed to push some refs to 'https://github.com/帳號名稱/command-line-lab.git' hint: Updates were rejected because the remote contains work that you do not hint: have locally. ...
-
取得 GitHub 上的修改,並與本機的修改合併:
git pull --no-rebase--no-rebase指定以合併(merge)的方式整合兩邊的修改:保留兩條歷史,再以一個合併的 commit 連接。另一種方式是 rebase,它把本機新增的 commit 暫時移開,接在 GitHub 最新的 commit 之後重新套用一次,歷史因此成為一直線;本次實作不使用 rebase。若只執行git pull,新版 Git 會先要求選擇整合方式,並顯示fatal: Need to specify how to reconcile divergent branches.後停止。由於兩邊修改了同一行,合併無法自動完成:
Auto-merging about-me.txt CONFLICT (content): Merge conflict in about-me.txt Automatic merge failed; fix conflicts and then commit the result.
-
以
code about-me.txt開啟檔案,可以看到 Git 加入的衝突標記。VS Code 會以顏色區分兩個版本,並在上方提供「Accept Current Change」(保留本機)、「Accept Incoming Change」(保留 GitHub)與「Accept Both Changes」(兩者都保留)等選項;也可以直接手動編輯。若使用其他編輯器且檔案原本已開啟,請先關閉再重新開啟。<<<<<<< HEAD 我想研究台語的語法。 ======= 我想研究客語的聲調。 >>>>>>> 2b55f62e69575191df2490cf5129a012b2badb75
<<<<<<< HEAD與=======之間是本機的版本,=======與>>>>>>>之間是 GitHub 上的版本。HEAD指本機目前的版本;最後一行的長字串是 GitHub 上那個 commit 的編號。將內容改成最後要保留的文字,刪除三行標記,然後存檔。存檔後,檔案中不應再有<<<<<<<、=======或>>>>>>>。整理的方式有幾種,例如兩邊都保留(即「Accept Both Changes」的結果):
我想研究台語的語法。 我想研究客語的聲調。
或把兩邊合併成一句:
我想研究台語的語法與客語的聲調。
只保留其中一邊也可以。重點是三行標記都已刪除,檔案中只剩最後要保留的文字。
-
完成合併並上傳:
git add about-me.txtgit commit -m "Resolve conflict in about-me.txt"git push此處執行
git add,是告知 Git 衝突已經解決。執行git log --oneline --graph,可以看到兩條分開的歷史在合併的 commit 匯合。
6. commit 的圖形表示
Learn Git Branching 以圖形呈現 commit 歷史。開啟後會直接進入「基礎篇」第一關〈介紹 git commit〉。閱讀說明並關閉視窗後,依指示執行兩次 git commit,圖中會新增兩個節點。
圖中的每一個節點,對應 git log --oneline 輸出中的一行。第 5 步的 git pull 已經產生過一次合併;建立分支與 rebase 的操作則不在本次實作範圍內。
Git 的使用情境
凡是會反覆修改、需要保留修改過程的檔案,都適合以 Git 管理。常見的情境如下:
- 個人寫作
- 以 LaTeX 或 Markdown 撰寫論文、講義時,每完成一段就 commit。
git diff可以逐行顯示兩個版本之間的差異,比人工對照兩份文件清楚得多(圖一)。 - 撰寫程式
- 在程式可以正常執行時 commit。之後若程式出錯,可以查看改了哪些地方,或回到先前可以執行的版本。
- 重大修改之前
- 先建立分支(branch),在分支上嘗試新的寫法。確定可行後再合併(merge)回主線;不可行就捨棄分支,主線不受影響。重要的版本可以加上標籤(tag),例如投稿版或 v1.0,日後可以直接回到該版本(圖二、圖五)。
- 與他人協作
- 每個人在各自的分支上工作,透過 GitHub 的 pull request 檢視彼此的修改,再決定是否合併(圖三)。
- 與 AI 代理人協作
- 讓代理人在獨立的分支上修改,先以
git diff檢視它改了什麼,再決定是否合併;結果不理想時,捨棄該分支即可。多個代理人同時開發不同的功能時,各自使用一個分支(或以git worktree建立各自的工作資料夾),彼此不會覆蓋對方的修改。代理人完成的 commit 也會記錄在歷史中(圖四)。
git diff:以行為單位,有改動的整行標為刪除(-)與新增(+)
diff --git a/paper.tex b/paper.tex--- a/paper.tex+++ b/paper.tex@@ -1,3 +1,3 @@11 \section{研究方法}2-本研究使用 BPE 切分台語語料。2+本研究比較 BPE 與 Unigram LM 兩種方法切分台語語料。33 語料來源為教育部臺灣台語常用詞辭典。
git diff --word-diff:同一個修改,只標出改動的字詞
1\section{研究方法}2[-本研究使用-]{+本研究比較+} BPE[-切分台語語料。-]{+與 Unigram LM 兩種方法切分台語語料。+}3語料來源為教育部臺灣台語常用詞辭典。
將滑鼠移到上方任一行,或點選該行,這裡會顯示該行的意義。
- 開頭的是刪除的行,+ 開頭的是新增的行;左側兩欄是行號,左欄為修改前、右欄為修改後,刪除的行只有左欄、新增的行只有右欄。下方為加上 --word-diff 的結果,以字詞為單位標示。experiment 分支確定可行後合併回 main;agent-idea 分支的結果不理想,因此捨棄,main 不受影響;v1.0 標籤標記投稿的版本。以下三張圖取自 Hugging Face 的 transformers 儲存庫。這是許多語言模型程式使用的開放原始碼套件,自 2018 年起以 Git 共同維護。截至 2026 年 9 月 30 日,它的規模如下:
- commit
- 約 24,000
- 貢獻者
- 約 4,100
- 已合併的 pull request
- 約 20,600
- 檢視中的 pull request
- 約 1,600
- 分支
- 約 1,600
- 版本標籤
- 289
若沒有版本控制,數千人只能輪流傳遞同一份檔案:一次只有一個人可以修改,其他人都必須等待,就像一次只允許一個人通過的號誌。Git 讓每個人在自己的分支上同時工作,修改完成後再經過檢視與合併,因此上千個修改可以同時進行。
fix-assisted 分支上完成修改,檢視通過後將 2 個 commit 合併到 main。Files changed 分頁以紅色與綠色標示刪除與新增的行,與圖一的 git diff 相同。
README.md 的修改歷史。每一筆 commit 都有說明、作者、日期與編號(如 64f3045),點選即可查看當次的 diff。第二筆的作者為 ydshieh and claude,是人與 AI 代理人共同完成的 commit。
v5.17.0)都是一個 tag,指向特定的 commit,任何人都可以下載當時的程式碼。相關指令:git switch -c experiment 建立並切換到新分支,git switch main 回到主線,git merge experiment 將分支合併回目前的分支,git tag v1.0 為目前的 commit 加上標籤,git log --oneline --graph --all 顯示所有分支的圖。本次實作只使用 main 分支;分支的練習見「延伸閱讀」中的 Learn Git Branching。
常見錯誤
| 訊息 | 原因與處理 |
|---|---|
Author identity unknown | 尚未設定使用者名稱與電子郵件。請完成「Git 與 GitHub 儲存庫」的第 6 步。 |
git push 要求輸入 Username、Password | 按 Ctrl + C 取消,執行 gh auth setup-git 後再 push 一次。 |
fatal: not a git repository | 目前不在 command-line-lab 中。以 pwd 確認位置後,執行 cd ~\Downloads\command-line-labcd ~/Downloads/command-line-lab。 |
pathspec 'about-me.txt' did not match any files | 檔案不在這個資料夾中、尚未存檔,或檔名實際上是 about-me.txt.txt、about-me.rtf。以 ls 查看實際檔名。 |
執行 git commit 後出現陌生的編輯畫面 | 未加 -m,Git 因此開啟了 Vim 編輯器。按 Esc,輸入 :q! 再按 Enter 離開,然後加上 -m 重新執行。 |
fatal: Need to specify how to reconcile divergent branches. | 改用 git pull --no-rebase。 |
! [rejected] ... (fetch first) | GitHub 上有本機沒有的 commit。先執行 git pull --no-rebase,再 push。 |
LF will be replaced by CRLF | 換行格式的提示,不影響操作。 |
| 帳號驗證或登入無法完成 | 請找助教協助,並先完成 commit 與正規式的部分,push 稍後再補。 |
正規式
正規式(regular expression,簡稱 regex)是描述文字樣式的表示法。一般的搜尋只能尋找固定的字串,例如「台灣」;正規式則可以描述一類字串,例如「以 09 開頭的十位數字」或「位於行首的學號」。
正規式常用於下列工作:在大量文字中找出符合某種樣式的字串,例如在語料庫中找出所有疊字;檢查資料的格式,例如電子郵件或電話號碼;從文字中擷取特定欄位;清理雜亂的文字,例如刪除網頁擷取時殘留的 HTML 標籤、把連續的空白合併成一個、統一全形與半形的標點;以及把文字切分成較小的單位。語言模型的 tokenizer 也在兩個階段使用正規式:先以正規式清理與統一文字(normalization),再以正規式把文字切成小段(pre-tokenization,見本節第 8 小節)。多數程式語言、文字編輯器與命令列工具都支援正規式。
以清理文字為例。下表把一行從網頁擷取的文字依序處理,每一步以一條正規式找出要處理的部分,再換成指定的內容。前三步屬於 normalization,最後一步屬於 pre-tokenization。表中各符號的意義在本節後半說明,此處只需比較每一步前後的差異。
| 步驟 | 正規式 | 取代為 | 處理後 |
|---|---|---|---|
| 原始文字 | <p>我想研究□台語的聲調␣,↵也想研究客語␣。</p> | ||
| 刪除 HTML 標籤 | <[^>]+> | (刪除) | 我想研究□台語的聲調␣,↵也想研究客語␣。 |
| 刪除空白與換行 | \s+ | (刪除) | 我想研究台語的聲調,也想研究客語。 |
| 半形逗號改為全形 | , | , | 我想研究台語的聲調,也想研究客語。 |
| 切分詞元(挑出) | \p{Script=Han}|\w+|[^\w\s] | 我想研究台語的聲調,也想研究客語。 |
將滑鼠移到表中正規式的任一部分,或點選該部分,這裡會顯示其意義。
表中 ␣ 代表半形空白,□ 代表全形空白,↵ 代表換行。\s 同時符合這三種字元。中文不以空白分詞,所以第二步直接刪除空白;處理英文時則應改為取代成一個半形空白。最後一步的規則即第 8 小節的「漢字逐字」。
本節使用 RegExr 撰寫正規式,符合的部分會即時標示出來。
1. 準備 RegExr
開啟本課準備的 RegExr 練習頁。畫面中央標示 Text 的大框中已有下列四行聯絡資料:
Alice Chen alice@example.com 0912-345-678
Bob Wang bob.wang@ntu.edu.tw 0928-123-456
Carol Lin carol99@gmail.com 0988-765-432
Office office@example.com 02-2345-6789上方 Expression 區的兩條斜線之間有一條範例正規式 [A-Z],先將它刪除,之後的正規式都寫在這兩條斜線之間。若練習頁無法開啟,可改開 RegExr 首頁,把 Text 框中原有的文字換成上面四行。
Lin 為例,符合的部分以藍色標示。2. 基本符號
正規式中的大多數字元代表字元本身,例如 Lin 就是比對 Lin 這三個字母。下列符號則有特殊意義,更完整的說明見正規式 ELI5:
| 符號 | 意義 | 範例 |
|---|---|---|
\d | 任一數字 | \d 比對 7 |
+ | 前一個元素出現一次以上 | \d+ 比對 2026 |
{n} | 前一個元素恰好出現 n 次 | \d{3} 比對 345 |
| | 或 | cat|dog 比對 cat 或 dog |
^ | 一行的開頭 | ^S 比對行首的 S |
3. 練習 動手做
-
使每一個數字各自成為一個比對結果。
提示
表示「任一數字」的寫法以反斜線開頭,共兩個字元。
-
使連續的數字成為一個比對結果。
提示
在上一題的寫法之後,加上表示「一個以上」的符號,並觀察
0912-345-678被分成幾段及其原因。 -
只比對三組手機號碼,不包含辦公室號碼
02-2345-6789。提示 1
手機號碼都以
09開頭,可直接照字面寫出。提示 2
{3}表示恰好三個。連字號-也照字面寫出。 -
比對
Alice或Bob。提示
「或」以直線
|表示。完成後,觀察信箱中的alice、bob是否被比對,並思考原因。
4. 行首 動手做
將 Text 替換為下列內容:
S001 Alice Chen
S002 Bob Wang
X003 Carol Lin先點選 Expression 右上角的 Flags,勾選 multiline。預設情況下,^ 只比對整段文字的開頭;啟用 multiline 後,^ 才會比對每一行的開頭。
/g 變為 /gm。練習:比對 S001 與 S002,但不包含 X003。完成後取消 multiline,觀察哪一行不再被比對。
提示
^ 表示一行的開頭。
5. 更多符號 動手做
下列符號與第 2 小節的符號可以任意組合:
| 符號 | 意義 | 範例 |
|---|---|---|
. | 任一字元(換行除外) | b.b 比對 bob |
* | 前一個元素出現零次以上 | ab* 比對 a、ab、abb |
? | 前一個元素可有可無 | colou?r 比對 color 與 colour |
[abc] | 方括號中的任一字元 | [aeiou] 比對任一母音字母 |
[a-z] | 範圍內的任一字元 | [A-Z] 比對任一大寫字母 |
[^abc] | 方括號中以外的任一字元 | [^\d] 比對任一非數字的字元 |
\w | 英文字母、數字或底線 | \w+ 比對 carol99 |
\s | 空白字元,包括空格、Tab 與換行 | \s+ 比對一串空白 |
\b | 詞的邊界,本身不佔字元 | \bLin\b 不比對 Linda 中的 Lin |
$ | 一行的結尾(須啟用 multiline) | \d+$ 比對行尾的數字 |
\. | 句點本身。其他特殊符號也以反斜線表示其本身,例如 \+、\? | \.tw 比對 .tw |
將 Text 換回第 1 小節的四行聯絡資料(重新開啟練習頁亦可),並保持 multiline 為勾選狀態。
-
比對每一行的第一個單字。
提示
行首加上表示「一個以上的英文字母或數字」的寫法。
-
比對每一行最後一組數字。
提示
以
$表示行尾。 -
比對全部四個電子郵件地址。
提示
信箱由英文字母、數字與句點組成,中間有一個
@。[\w.]表示「\w或句點」;在方括號中,句點只代表句點本身。 -
只比對以
.com結尾的電子郵件地址。提示
在方括號以外,句點代表任一字元,因此
.com的句點須寫成\.。
6. 群組與反向參照 動手做
以圓括號 ( ) 括起的部分稱為群組(group)。群組有兩種用途:一是把多個字元視為一個整體,例如 (ha)+ 比對 ha、haha、hahaha;二是記住該部分實際符合的內容,稱為擷取(capture)。由左至右,第一個群組擷取的內容以 \1 表示,第二個以 \2 表示,依此類推。在正規式中寫 \1 稱為反向參照(backreference),表示「與第一個群組擷取的內容相同」。只需要整體、不需要擷取時,可以寫成 (?:ha)+。
取代。擷取的內容也可以用於取代。點選 RegExr 畫面下方 Tools 區的 Replace,在其中輸入取代後的格式,$1 代表第一個群組擷取的內容,$2 代表第二個。以四行聯絡資料為例:
-
以 Expression
^(\w+) (\w+)與 Replace$2, $1,把Alice Chen改寫成Chen, Alice。觀察Office那一行為何沒有改變。提示
比較
Office之後與Alice之後的字元。
疊字。將 Text 換成下列內容:
他高高興興地走了。
這件事我們研究研究再說。
天天下雨,路上慢慢走。
價格是 120 元,運費 60 元。-
比對所有連續兩個相同的字,例如
天天。提示
以
(.)擷取任一個字,再以\1要求下一個字與它相同。 -
比對 AABB 式的疊字,例如
高高興興。提示
需要兩個群組,分別以
\1與\2參照。 -
比對 ABAB 式的疊字,例如
研究研究。提示
先擷取兩個字,再依相同的順序重複一次。
7. 萬用字元與正規式
終端機中的檔名也可以用樣式表示,這種樣式稱為萬用字元(wildcard,又稱 glob)。例如 ls data/*.csv 會列出 data 中所有以 .csv 結尾的檔案。在 macOS 的終端機中,萬用字元由 shell 在執行指令之前展開成符合的檔名清單。在 PowerShell 中,萬用字元由各個指令自行處理,結果相同。
萬用字元與正規式使用部分相同的符號,但意義不同:
| 符號 | 萬用字元 | 正規式 |
|---|---|---|
* | 任意長度的任意字元 | 前一個元素重複零次以上;「任意長度的任意字元」須寫成 .* |
? | 任意一個字元 | 前一個元素可有可無;「任意一個字元」須寫成 . |
[0-9] | 一個數字 | 一個數字 |
兩者是不同的表示法,萬用字元並不是正規式的一部分:
- 萬用字元必須符合整個檔名,相當於正規式前後加上
^與$;正規式則會在文字中的任何位置尋找符合的部分。 - 萬用字元中的
.只代表句點;正規式中的.代表任意一個字元。 - 萬用字元沒有
+、{3}、|、\d等寫法,能描述的樣式少得多。
下列指令須在 week04-lab 中執行,可使用「在資料夾之間移動」的練習用終端機,或在自己的終端機中先執行 cd ~\Downloads\week04-labcd ~/Downloads/week04-lab。第三行須先建立 practice。
ls data/*.csvls data/results_202?.csvcp data/*.csv practice/8. 以正規式切分詞元 頁面練習
語言模型處理文字之前,先由 tokenizer 把文字切成一個個詞元(token),模型所認得的全部詞元稱為詞彙表(vocabulary)。tokenizer 先清理與統一文字(normalization,見本節開頭的表),接著進行 pre-tokenization,通常以一條正規式把文字切成小段(詳見 Lecture 03)。正規式在此有兩種用法:「切開」以符合的部分作為分界,丟掉分界,保留分界之間的文字;「挑出」只保留符合的部分,丟掉其餘的文字。規則描述的是分界時用「切開」,描述的是詞元本身時用「挑出」。以 I don't know. 為例:
- 以
\s+切開:得到I、don't、know.。空白是分界,標點仍連在字上。 - 以
\w+挑出:得到I、don、t、know。撇號與句點不符合\w,因此被丟掉。
「切開」與「挑出」分別相當於 Python 的 re.split 與 re.findall。GPT-2 的規則與本節開頭表格的最後一步都屬於「挑出」。
討論
- 以
\s+切開英文句子時,don't與words;的結果為何? - 以同一條規則切開中文句子,會得到幾個詞元?原因為何?
- 選擇「\w+ 挑中文」範例:為什麼
\w+無法比對任何中文字? - GPT-2 的規則把空白歸到哪一側?這對詞彙表有什麼影響?
pre-tokenization 的詳細說明見 pre-tokenization ELI5。Hugging Face 的 tokenizers 文件說明完整的流程:normalization、pre-tokenization、模型切分與後處理(post-processing),其中前兩個步驟都可以用正規式設定(normalizers.Replace 與 pre_tokenizers.Split)。
9. LLM 的 tokenizer
本頁的工具只做到 pre-tokenization,也就是以一條規則把文字切成小段。實際的 LLM tokenizer 還會再把每一小段切成詞彙表中的單位,並把每個詞元換成一個編號(ID),模型處理的就是這串編號。與正規式相比,主要有三點不同:
- 切分方式由資料決定。詞彙表由 BPE 等演算法從大量語料中統計常見的字元組合而得,而不是由人撰寫規則。常見的詞成為一個詞元,少見的詞則拆成幾段,例如 GPT-2 把
unbelievably切成un、bel、iev、ably(詳見 BPE ELI5)。 - 以位元組為最小單位。GPT 系列的 tokenizer 先把文字轉為 UTF-8 位元組,因此任何文字都能表示。詞彙表中沒有的字會拆成位元組:「詞」的 UTF-8 編碼是
e8 a9 9e三個位元組,在 GPT-2 中就成為三個詞元。 - 不同模型的切法不同。詞彙表的大小與內容依模型而異。下表比較 GPT-2(詞彙表 50,257 個詞元)與 GPT-4o(約 200,000 個)對同樣兩句話的切分,灰色方塊是無法單獨顯示為文字的位元組:
| 模型 | 切分結果 | 詞元數 |
|---|---|---|
| GPT-2 | Tokenizers␣don't␣see␣words;␣they␣see␣strings. | 11 |
| GPT-4o | Tokenizers␣don't␣see␣words;␣they␣see␣strings. | 10 |
| GPT-2 | e8a99ee5 8583不是e8a99eefbc8ce4 b99f不是e8 aa9ee7b4a0。 | 23 |
| GPT-4o | 詞元不是詞,也不是語素。 | 9 |
同一句中文,GPT-2 需要 23 個詞元,GPT-4o 只需要 9 個。詞元數決定了模型能讀入的文字長度與使用 API 的費用,因此詞彙表涵蓋哪些語言,會直接影響該語言的使用成本。各模型的切分結果可在 Tiktokenizer 中比較。
10. 正規式的限制
正規式適合描述局部、固定的字串樣式,但有下列限制:
- 只處理形式,不處理意義。
(.)\1能找出天天、慢慢,(.)(.)\1\2能找出研究研究,但無法判斷這些疊字分別屬於名詞、形容詞或動詞,也無法判斷多音字「行」在句中讀作 xíng 或 háng。 - 無法切分中文詞。「研究生命起源」可以切成「研究/生命/起源」,也可以切成「研究生/命/起源」。正規式只能依字元的種類切分,判斷哪一種切法正確則需要詞典或統計模型。這也是 tokenizer 以 BPE 等方法從資料中學習詞元的原因之一。
- 無法處理任意深度的巢狀結構。例如成對的括號
((()))、層層包覆的 HTML 標籤,或英文的中心嵌入句 The rat the cat the dog chased bit died。這類結構需要記住「開了幾層」,而正規式沒有計數的能力。在形式語言理論中,正規式描述的是正規語言(regular language),位於喬姆斯基階層(Chomsky hierarchy)的最低一層;巢狀結構屬於上一層的上下文無關語言(context-free language),須以剖析器(parser)處理。反向參照雖然使實際的正規式超出正規語言的範圍,例如^(.+)\1$可比對「同一字串重複兩次」,但 JavaScript 與 Python 的re仍無法處理任意深度的巢狀結構。 - 不易閱讀與維護。上方 GPT-2 的規則已不易一眼看懂。規則越長,越難確認它涵蓋了所有情況,也越難修改。某些寫法在特定輸入下還會因反覆回溯(backtracking)而執行得極慢,例如以
(a+)+$比對一長串a之後接一個b的文字。 - 不處理字元的編碼差異。外觀相同的字元可能有不同的編碼,例如全形與半形的標點,或以組合字元表示的帶調字母。比對之前須先統一文字,即本節開頭表格中的 normalization。
因此,需要結構、意義或統計的工作,通常交給剖析器、詞典或語言模型處理,正規式則負責前後的清理與擷取。
綜合練習 動手做
本練習結合終端機、正規式與 Git。在 command-line-lab 中以 code participants.txt 建立檔案,寫入下列內容並存檔:
S001 Alice Chen
S002 Bob Wang
X003 Carol Lin第 1 項提示
先以 pwd 確認位於 command-line-lab,再依序使用 ls 與 cat。
第 5 項提示
git add 之後可以一次接多個檔名,以空白分隔。commit 訊息可以自訂。
完成後的儲存庫
command-line-lab/ ├── README.md ├── about-me.txt ├── participants.txt └── answers.txt
此外,應能對照 Git 的四個區域,說明下列四種狀態的差異:已修改但尚未 add、已 add 但尚未 commit、已 commit 但尚未 push,以及已上傳到 GitHub。
延伸閱讀
- GitHub Skills: Introduction to Git:GitHub 官方的互動課程。將練習儲存庫複製到自己的帳號後,依指示逐步完成,內容包含 commit、歷史紀錄與分支;課程使用 VS Code。
- Gitualize:各個 Git 指令的說明與動畫示範,包含本次未詳細說明的
pull、branch與merge。 - Learn Git Branching:分支與合併的互動練習。
- RegexOne:正規式分題練習。
- Software Carpentry: The Unix Shell:命令列入門教材。教材使用 Unix 的選項,其中部分在 PowerShell 中不適用。
參考答案
逐步移動
pwd輸出C:\Users\帳號名稱\Downloads\week04-lab/Users/帳號名稱/Downloads/week04-lab,最後一段為week04-lab。- 共三項:
README.txt、data、notes。 - 藍底移到
data,pwd的輸出末端增加data。 ..表示上一層資料夾,因此回到week04-lab。week04-lab之下新增practice。- 仍然存在。
cp是複製,不影響原檔。 - 一個:
README.txt。 You found the README. If you can read this sentence in your terminal, cat works.README-copy.txt。mv同時用於移動與重新命名。- 仍為
week04-lab。..一律指目前資料夾的上一層。
練習:自行移動的檢核:從 data 或 practice 執行 cd ..,都會回到 week04-lab,因為兩者都位於 week04-lab 之內。
Git 的四個區域
| 操作 | 修改所在區域 | git status 的輸出 |
|---|---|---|
| 存檔 | 工作資料夾 | Untracked files(新檔案)或 Changes not staged for commit(已追蹤的檔案) |
git add | 暫存區 | Changes to be committed |
git commit | 本機歷史(GitHub 尚未更新) | Your branch is ahead of 'origin/main' by 1 commit |
git push | GitHub | Your branch is up to date with 'origin/main' |
正規式練習
\d。每個數字各自為一個比對結果,包含carol99中的9。\d+。+會盡可能比對更多字元。0912-345-678被分為三段,因為-不是數字。09\d{2}-\d{3}-\d{3}。02-2345-6789不以09開頭,因此不會被比對。Alice|Bob。信箱中的alice、bob不會被比對,因為正規式預設區分大小寫;在 Flags 勾選 case insensitive 後即會比對。- 行首:
^S\d{3},並須啟用 multiline。若取消 multiline,只有S001會被比對,因為^只比對整段文字的開頭。
09\d{2}-\d{3}-\d{3} 畫出的鐵路圖。由左至右沿著線走一遍,經過的方框依序組成一個符合的字串。每個 digit 方框代表一位數字,下方的迴圈表示走過方框之後再回頭重複,迴圈上標示的是重複的次數:once 是一位再重複一次,共兩位;2 times 是一位再重複兩次,共三位。只寫 S\d{3} 在這份資料中也能得到正確結果,但也會比對到行中的 XS004 這類字串;加上 ^ 可以明確限定位置。
更多符號、群組與反向參照
^\w+,比對Alice、Bob、Carol、Office。須啟用 multiline。\d+$,比對678、456、432、6789。須啟用 multiline。[\w.]+@[\w.]+。[\w.]+@\w+\.com,比對三個信箱,不包含bob.wang@ntu.edu.tw。若寫成.com,句點會比對任一字元。
群組與反向參照:
- 前三行改寫成
Chen, Alice、Wang, Bob、Lin, Carol。Office之後是一串空白,接著才是信箱;正規式中兩個\w+之間只允許一個空格,因此這一行不符合。 (.)\1,比對高高、興興、天天、慢慢。(.)\1(.)\2,比對高高興興。(.)(.)\1\2,比對研究研究。
討論:以正規式切分詞元
don't維持為一個詞元,words;的分號也附在詞後。以空白切分只依據空白,不處理標點。- 一個,即整個句子。中文書寫不以空白分詞,因此沒有分界點。
- 在 JavaScript(RegExr 即使用此規則)中,
\w只相當於[A-Za-z0-9_],因此無法比對漢字;比對漢字須使用\p{Script=Han}。Python 3 的re則預設支援 Unicode,\w可以比對漢字,但\p{Script=Han}須改用regex套件。 - 空白附在後一個詞的前面,例如
␣see、␣they。因此同一個詞出現在句首(前面沒有空白)與句中(前面有空白)時,例如see與␣see,在詞彙表中是兩個不同的詞元。
綜合練習:answers.txt 範例
^S\d{3}
flag: m (multiline)
第 5 項可以執行:
git add participants.txt answers.txt git commit -m "Add exit task" git push