17 自動化與腳本
讓重複的事消失。任何做過兩次以上的事,都值得自動化。
17.1 自動化思維
問自己:
- 這件事我會再做嗎?
- 做這件事需要多少步驟?
- 自動化需要花多少時間?
如果 重複次數 × 每次時間 > 自動化時間,就自動化它。
17.2 常見自動化場景
17.2.1 Git 工作流
背景(問題發現)
每天管理 dotfiles 和多個專案時,我們會重複執行相同的 Git 操作:
- 切換到 dotfiles 目錄
- 加入所有更改
- 寫 commit message(最花時間)
- 推送到遠端
- 回到原目錄
這個流程每天可能重複 5-10 次,每次耗時 1-2 分鐘。特別是撰寫有意義的 commit message 需要思考和打字。
方法
建立兩個自動化函數來簡化 Git 工作流:
dp()- 專門用於 dotfiles 的快速提交- 自動切換到 dotfiles 目錄
- 使用 AI 工具自動生成 commit message
- 推送後返回原目錄
zgit()- 當前專案的快速提交- 在當前目錄執行
- 使用 conventional commits 格式
- 適合遵循團隊規範的專案
結果(程式碼)
討論/延伸
注意事項: - 需要先安裝 aicommits 工具(npm install -g aicommits) - 確保 $DOTFILES 環境變數已設定 - cd - 會返回到前一個工作目錄
變體與改進: - 可加入 git status 檢查是否有未提交的更改 - 加入錯誤處理:如果 push 失敗要顯示訊息 - 可以加入確認步驟,避免誤推送
進一步學習: - 了解 Conventional Commits 規範 - 探索其他 AI commit 工具如 git-cliff、commitizen - 學習 Git hooks 來自動執行檢查
17.2.2 專案初始化
背景(問題發現)
每次開始新專案時,我們都需要:
- 從 GitHub clone 模板專案
- 重新命名目錄
- 安裝所有依賴套件
- 清除舊的 Git 歷史
- 重新初始化 Git repository
- 建立第一個 commit
這個流程涉及至少 8-10 個指令,容易遺漏步驟或打錯指令。特別是使用同一個模板重複建立專案時(例如使用 Claude Artifact Runner 建立多個實驗專案),每次都要重複相同的操作。
方法
建立一個互動式函數 cloneclaude(),自動化整個專案初始化流程:
- 使用 GitHub CLI (
gh) clone 模板 repository - 透過
read指令互動式詢問新專案名稱 - 自動執行重新命名、安裝依賴、Git 初始化等步驟
- 提供完成訊息確認所有步驟成功
結果(程式碼)
function cloneclaude() {
cd $HOME
# Clone 模板專案
gh repo clone htlin222/claude-artifact-runner
# 詢問新名稱
echo -n "Enter new folder name: "
read -r new_folder_name
mv claude-artifact-runner "$new_folder_name"
cd "$new_folder_name"
# 安裝依賴
npm install
# 重新初始化 Git
rm -rf .git
git init
git add .
git commit -m 'init'
echo "Project setup complete!"
}討論/延伸
注意事項: - 需要安裝並認證 GitHub CLI (gh auth login) - 確保網路連線穩定,避免 clone 或 npm install 中斷 - rm -rf .git 會永久刪除原始 Git 歷史,請確認是否需要保留
變體與改進: - 加入錯誤處理:檢查 clone 是否成功 - 驗證新專案名稱是否已存在,避免覆蓋 - 支援更多模板選擇(傳入參數選擇不同模板) - 自動開啟 VSCode:code . - 詢問是否建立 GitHub repository:gh repo create
範例擴充版本:
function cloneclaude() {
local template="${1:-htlin222/claude-artifact-runner}"
gh repo clone "$template" || return 1
echo -n "Enter new folder name: "
read -r new_folder_name
[[ -d "$new_folder_name" ]] && {
echo "Error: Folder already exists!"
return 1
}
mv "${template##*/}" "$new_folder_name"
cd "$new_folder_name" || return 1
npm install && rm -rf .git && git init && git add . && \
git commit -m 'init' && code .
}進一步學習: - 探索 cookiecutter 或 degit 等專案模板工具 - 研究如何建立自己的專案模板 - 了解 GitHub Template Repository 功能
17.2.3 檔案整理
背景(問題發現)
下載資料夾或專案目錄經常累積大量零散檔案:
- 螢幕截圖、PDF、圖片、文件等混在一起
- 檔名沒有統一格式,難以搜尋
- 找特定日期的檔案需要手動檢查每個檔案的修改時間
- 手動建立資料夾並移動檔案耗時且容易出錯
例如一個下載資料夾可能有 50+ 個檔案,手動整理需要 10-15 分鐘。
方法
建立 chore() 函數自動按照檔案修改日期整理檔案:
- 掃描當前目錄的所有檔案(不含符號連結)
- 讀取每個檔案的修改時間
- 使用「日期_檔名」格式建立資料夾
- 將檔案移動到對應的資料夾中
- 跳過已經有日期前綴的檔案,避免重複處理
核心技術: - date -r "$file" 讀取檔案修改時間 - 正則表達式 ^[0-9]{4}-[0-9]{2}-[0-9]{2}_ 判斷是否已有日期前綴 - ${file%.*} 移除副檔名
結果(程式碼)
# 按日期整理檔案
function chore() {
for file in *; do
if [[ -f "$file" && ! -L "$file" ]]; then
mod_date=$(date -r "$file" +"%Y-%m-%d")
if [[ ! "$file" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}_ ]]; then
filename_no_ext="${file%.*}"
new_folder="${mod_date}_${filename_no_ext}"
mkdir -p "$new_folder"
mv "$file" "$new_folder/"
echo "Moved $file to $new_folder/"
fi
fi
done
}討論/延伸
注意事項: - 此函數會修改目錄結構,建議先在測試目錄執行 - 只處理檔案,不處理子目錄 - 符號連結會被跳過(! -L "$file") - 已有日期前綴的檔案不會被重新處理
變體與改進: - 按月份分類:改為 +"%Y-%m" 格式 - 按檔案類型分類:加入副檔名判斷 - 乾執行模式:加入 --dry-run 參數預覽結果 - 支援遞迴處理子目錄
範例擴充版本(按類型和日期分類):
function chore-type() {
for file in *; do
if [[ -f "$file" && ! -L "$file" ]]; then
ext="${file##*.}"
mod_date=$(date -r "$file" +"%Y-%m")
case "$ext" in
jpg|png|gif|jpeg) type="images" ;;
pdf) type="documents" ;;
mp4|mov|avi) type="videos" ;;
*) type="others" ;;
esac
new_folder="${type}/${mod_date}"
mkdir -p "$new_folder"
mv "$file" "$new_folder/"
echo "Moved $file to $new_folder/"
fi
done
}進一步學習: - 研究 find 指令的進階用法(按時間、大小、類型搜尋) - 了解 rsync 用於批次檔案操作 - 探索 Hazel(macOS)等自動化檔案整理工具
17.2.4 媒體處理
背景(問題發現)
處理多媒體檔案時經常遇到兩個場景:
- 下載音樂:想從 YouTube 下載音訊檔案(例如演講、音樂、Podcast),但只需要音訊不需要影片
- 手動使用線上工具:廣告多、品質不穩定、隱私疑慮
- 下載完整影片再轉檔:浪費時間和頻寬
- 合併影片:需要將多個影片片段合併成一個檔案(例如課程錄影、會議記錄)
- 使用影片編輯軟體:開啟慢、操作複雜、檔案可能重新編碼導致品質損失
- 手動處理 10 個片段可能需要 30 分鐘以上
方法
建立兩個命令列工具函數:
yt-mp3()- YouTube 音訊下載器- 使用 yt-dlp 工具(比
youtube-dl更快更穩定) --extract-audio只提取音訊軌道--audio-format mp3轉換為通用的 MP3 格式-o "%(title)s.%(ext)s"使用影片標題作為檔名
- 使用 yt-dlp 工具(比
joinmp4()- 影片合併工具- 使用 ffmpeg 的 concat demuxer(不重新編碼,速度快)
- 掃描當前目錄所有
.mp4檔案 - 建立
filelist.txt作為合併清單 -c copy直接複製串流,不重新編碼(保持原始品質)
結果(程式碼)
討論/延伸
注意事項: - 需要安裝 yt-dlp(brew install yt-dlp 或 pip install yt-dlp) - 需要安裝 ffmpeg(brew install ffmpeg) - YouTube 下載需遵守版權法規和 YouTube 使用條款 - joinmp4() 會合併所有 .mp4 檔案,確認目錄中只有需要的檔案 - 檔案會按檔名排序,可能需要先重新命名(例如 01.mp4, 02.mp4)
變體與改進:
yt-mp3 進階版本:
joinmp4 進階版本:
# 支援自訂輸出檔名和檔案順序確認
function joinmp4() {
local output="${1:-combined.mp4}"
# 顯示將要合併的檔案順序
echo "Files to be merged (in order):"
for file in *.mp4; do
echo " - $file"
echo "file '$file'" >> filelist.txt
done
read -p "Continue? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
ffmpeg -f concat -safe 0 -i filelist.txt -c copy "$output"
echo "✅ Merged to $output"
fi
rm filelist.txt
}其他有用的媒體處理函數:
# 影片轉 GIF
function vid2gif() {
ffmpeg -i "$1" -vf "fps=10,scale=640:-1:flags=lanczos" \
-c:v gif "${1%.*}.gif"
}
# 壓縮影片(減小檔案大小)
function compress-vid() {
ffmpeg -i "$1" -vcodec libx264 -crf 28 "${1%.*}_compressed.mp4"
}
# 提取影片片段
function clip-vid() {
local input="$1"
local start="$2" # 格式:00:01:30
local duration="$3" # 格式:00:00:45
ffmpeg -i "$input" -ss "$start" -t "$duration" -c copy \
"${input%.*}_clip.mp4"
}進一步學習: - 深入學習 ffmpeg 官方文件 - 探索 yt-dlp 的進階功能(字幕下載、播放清單、格式選擇) - 了解影片編碼參數(CRF、bitrate、codec)對品質和檔案大小的影響
17.3 定時任務
17.3.1 macOS launchd
背景(問題發現)
許多重要任務需要定期執行:
- 每日備份重要資料(dotfiles、專案、文件)
- 定期清理暫存檔案和下載資料夾
- 自動更新依賴套件和系統工具
- 定期檢查系統健康狀況
使用 cron 在 macOS 上有限制(系統睡眠時不執行、電源管理問題),而手動執行容易忘記。macOS 的官方解決方案是 launchd,它能在系統喚醒後執行錯過的任務,並且有更好的系統整合。
方法
使用 macOS 的 launchd 系統建立定時任務:
- 在
~/Library/LaunchAgents/建立.plist設定檔 - 定義任務標籤(Label)作為唯一識別
- 指定要執行的程式和參數
- 設定執行時間(時、分)
- 使用
launchctl載入和管理任務
關鍵欄位說明: - Label: 唯一識別名稱(反向網域命名) - ProgramArguments: 要執行的指令(陣列格式) - StartCalendarInterval: 定時執行的時間點
結果(程式碼)
建立 ~/Library/LaunchAgents/com.user.backup.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.user.backup</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/path/to/backup.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>2</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
</dict>
</plist>載入任務:
討論/延伸
注意事項: - 路徑必須使用絕對路徑(包含腳本路徑和指令路徑) - 檔案權限要正確:chmod 644 com.user.backup.plist - 使用 launchctl list | grep backup 檢查任務是否載入成功 - 錯誤訊息會記錄在 ~/Library/Logs/ 或系統 Console.app
常用 launchctl 指令:
進階設定範例:
每小時執行:
每週一上午 9 點:
監控檔案變化自動執行:
標準輸出/錯誤記錄:
實用範例:定期清理下載資料夾
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.user.cleanup-downloads</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/find</string>
<string>/Users/username/Downloads</string>
<string>-type</string>
<string>f</string>
<string>-mtime</string>
<string>+30</string>
<string>-delete</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>3</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
</dict>
</plist>進一步學習: - 閱讀 man launchd.plist 了解所有可用選項 - 探索 LaunchControl GUI 工具來管理 launchd 任務 - 研究系統級任務(/Library/LaunchDaemons/)與用戶級任務的差異
17.3.2 Linux cron
背景(問題發現)
在 Linux 系統上,定時任務通常使用 cron 來管理。與 macOS 的 launchd 類似,我們需要:
- 定期備份資料
- 自動清理日誌檔案
- 定時更新系統套件
- 執行健康檢查腳本
直接記住 cron 語法並不容易(五個欄位的時間格式),而且錯誤的設定可能導致任務無法執行或執行時間錯誤。
方法
使用 cron daemon 的 crontab 工具管理定時任務:
- 使用
crontab -e編輯當前使用者的 crontab 檔案 - 使用五欄位時間格式:
分 時 日 月 週 - 每行一個任務,包含時間和要執行的指令
- 系統會在指定時間自動執行腳本
Cron 時間格式說明:
* * * * *
│ │ │ │ │
│ │ │ │ └─── 週幾 (0-7,0 和 7 都是週日)
│ │ │ └──────── 月份 (1-12)
│ │ └───────────── 日期 (1-31)
│ └────────────────── 小時 (0-23)
└─────────────────────── 分鐘 (0-59)
結果(程式碼)
討論/延伸
注意事項: - 使用絕對路徑(cron 的 PATH 環境變數可能不完整) - Cron 執行時的環境變數與登入 shell 不同,可能需要在腳本中設定環境 - 預設不會發送輸出,使用 > 重導向或設定 MAILTO 接收通知 - 使用 crontab -l 列出現有任務,避免覆蓋
常用 crontab 指令:
常見時間設定範例:
# 每分鐘執行
* * * * * /path/to/script.sh
# 每小時的第 0 分執行
0 * * * * /path/to/script.sh
# 每天早上 8:30 執行
30 8 * * * /path/to/script.sh
# 每週一早上 9:00 執行
0 9 * * 1 /path/to/script.sh
# 每月 1 號凌晨 2:15 執行
15 2 1 * * /path/to/script.sh
# 每 5 分鐘執行一次
*/5 * * * * /path/to/script.sh
# 每天 8-17 點的每小時執行
0 8-17 * * * /path/to/script.sh
# 週一到週五早上 9 點執行
0 9 * * 1-5 /path/to/script.sh實用範例:完整的 crontab 設定
# 設定環境變數
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=your-email@example.com
# 每天凌晨 2 點備份
0 2 * * * /home/user/scripts/backup.sh >> /var/log/backup.log 2>&1
# 每週日凌晨 3 點清理舊日誌(保留 30 天)
0 3 * * 0 find /var/log -name "*.log" -mtime +30 -delete
# 每小時檢查磁碟空間
0 * * * * df -h | grep -E '^/dev/' | awk '$5 > 90 {print "WARNING: " $6 " is " $5 " full"}' | mail -s "Disk Space Alert" admin@example.com
# 每 15 分鐘檢查服務狀態
*/15 * * * * systemctl is-active --quiet nginx || systemctl restart nginx使用特殊字串(更易讀):
@reboot /path/to/script.sh # 系統啟動時執行
@yearly /path/to/script.sh # 等同於 0 0 1 1 *
@annually /path/to/script.sh # 同 @yearly
@monthly /path/to/script.sh # 等同於 0 0 1 * *
@weekly /path/to/script.sh # 等同於 0 0 * * 0
@daily /path/to/script.sh # 等同於 0 0 * * *
@midnight /path/to/script.sh # 同 @daily
@hourly /path/to/script.sh # 等同於 0 * * * *除錯技巧:
進一步學習: - 使用 crontab.guru 線上工具驗證 cron 語法 - 探索 anacron(適合非 24 小時運行的系統) - 研究 systemd timers(現代 Linux 的替代方案)
17.4 通知整合
背景(問題發現)
執行長時間任務時(例如編譯、測試、部署),我們經常遇到這些問題:
- 切換到其他視窗工作,忘記檢查任務是否完成
- 需要定期回來查看終端機輸出
- 任務失敗時沒有立即發現,浪費時間
- 想在任務完成時收到提醒,但不想一直盯著螢幕
例如 npm run build 可能需要 5-10 分鐘,這段時間可以做其他事,但需要知道何時完成。
方法
建立 notify() 函數整合 macOS 通知中心:
- 使用
osascript執行 AppleScript 指令 display notification顯示系統通知- 接受兩個參數:標題和訊息內容
- 與 shell 的
&&運算子結合,在指令成功後發送通知
核心技術: - local 宣告區域變數 - osascript -e 執行單行 AppleScript - && 確保前一個指令成功才執行通知
結果(程式碼)
討論/延伸
注意事項: - 僅適用於 macOS(需要 osascript) - 需要允許終端機發送通知(系統偏好設定 → 通知) - 使用 && 只在任務成功時通知,失敗則不會觸發 - 通知會出現在通知中心,可點擊查看
變體與改進:
支援成功/失敗通知:
加入聲音提示:
跨平台通知函數:
function notify() {
local title="$1"
local message="$2"
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
osascript -e "display notification \"$message\" with title \"$title\""
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
# Linux (需要 libnotify)
notify-send "$title" "$message"
else
# Windows (WSL)
powershell.exe -Command "New-BurntToastNotification -Text '$title', '$message'"
fi
}整合計時功能:
function notify-time() {
local start_time=$(date +%s)
"$@" # 執行傳入的指令
local exit_code=$?
local end_time=$(date +%s)
local duration=$((end_time - start_time))
if [ $exit_code -eq 0 ]; then
notify "Task Complete" "✅ Finished in ${duration}s"
else
notify "Task Failed" "❌ Failed after ${duration}s (exit code: $exit_code)"
fi
return $exit_code
}
# 使用方式
notify-time npm run build
notify-time pytest tests/進階:帶進度的長時間任務:
整合 Slack/Discord 通知:
實用組合範例:
進一步學習: - 探索 terminal-notifier 功能更強大的通知工具 - 研究 macOS 的 AppleScript 自動化功能 - 了解如何整合第三方通知服務(Pushover, Pushbullet)
17.5 Makefile 自動化
使用:
17.6 實作練習
- 為你最常做的工作建立自動化函數
- 設定定時備份腳本
- 建立專案的 Makefile
自動化不是一次性的事。持續觀察你的工作流,找出可以自動化的機會。