Skip to content

Instantly share code, notes, and snippets.

@yossuli
Last active February 18, 2026 11:12
Show Gist options
  • Select an option

  • Save yossuli/b27b64ebdac4507948240ee09d1e4bcd to your computer and use it in GitHub Desktop.

Select an option

Save yossuli/b27b64ebdac4507948240ee09d1e4bcd to your computer and use it in GitHub Desktop.
#!/bin/bash
export LANG=ja_JP.UTF-8
export LC_ALL=ja_JP.UTF-8
HISTORY_FILE="$HOME/.clipboard_history"
CONFIG_FILE="$HOME/.config/clipboard-manager/config"
# 設定ファイルの読み込み(デフォルト値)
NOTIFY_ON_PASTE=true
NOTIFY_ON_ERROR=true
# 設定ファイルが存在すれば読み込む
if [[ -f "$CONFIG_FILE" ]]; then
# shellcheck source=/dev/null
source "$CONFIG_FILE"
fi
check_history() {
if [[ ! -f "$HISTORY_FILE" ]] || [[ ! -s "$HISTORY_FILE" ]]; then
osascript -e 'display dialog "クリップボード履歴が空です" buttons {"OK"} default button 1' 2>/dev/null
exit 0
fi
}
# プレビューデータ生成とAppleScriptリスト変換
build_preview_list() {
PREVIEW_DATA=$(tail -r "$HISTORY_FILE" | /usr/bin/jq -r '(.timestamp[11:16]) + " - " + (.content | gsub("\n"; "↵") | .[0:60])' 2>/dev/null) || true
if [[ -z "$PREVIEW_DATA" ]]; then
osascript -e 'display dialog "クリップボード履歴が空です" buttons {"OK"} default button 1' 2>/dev/null
exit 0
fi
AS_LIST=""
LIST_ITEM_COUNT=0
while IFS= read -r line; do
[[ -z "$line" ]] && continue
ESCAPED=${line//\\/\\\\}
ESCAPED=${ESCAPED//\"/\\\"}
if [[ -n "$AS_LIST" ]]; then
AS_LIST="${AS_LIST}, \"${ESCAPED}\""
else
AS_LIST="\"${ESCAPED}\""
fi
LIST_ITEM_COUNT=$((LIST_ITEM_COUNT + 1))
done <<< "$PREVIEW_DATA"
}
# --- 編集モード ---
run_edit() {
check_history
ENTRY_COUNT=$(wc -l < "$HISTORY_FILE" | tr -d ' ')
build_preview_list
# 末尾に全クリアオプション
EDIT_LIST="${AS_LIST}, \"--- 全履歴をクリア ---\""
SELECTED_INDICES=$(osascript 2>/dev/null <<EOF
set historyList to {${EDIT_LIST}}
set selectedItems to choose from list historyList with prompt "削除するエントリを選択(複数選択可):" with title "Clipboard History - 編集" with multiple selections allowed
if selectedItems is false then
return "-1"
end if
set indexList to ""
repeat with sel in selectedItems
repeat with i from 1 to count of historyList
if item i of historyList is (sel as text) then
if indexList is "" then
set indexList to ((i - 1) as text)
else
set indexList to indexList & "," & ((i - 1) as text)
end if
exit repeat
end if
end repeat
end repeat
return indexList
EOF
) || true
[[ -z "$SELECTED_INDICES" || "$SELECTED_INDICES" == "-1" ]] && return
# 全クリアが含まれているかチェック
if echo ",$SELECTED_INDICES," | grep -q ",$LIST_ITEM_COUNT,"; then
CONFIRM=$(osascript 2>/dev/null <<EOF
display dialog "全てのクリップボード履歴を削除しますか?" buttons {"キャンセル", "全削除"} default button "キャンセル" with icon caution with title "Clipboard History"
return button returned of result
EOF
) || true
if [[ "$CONFIRM" == "全削除" ]]; then
: > "$HISTORY_FILE"
osascript -e 'display notification "全履歴を削除しました" with title "Clipboard History"' 2>/dev/null
fi
return
fi
# 選択インデックスを元ファイルの行番号に変換して削除
LINES_TO_DELETE=""
IFS=',' read -ra INDICES <<< "$SELECTED_INDICES"
for idx in "${INDICES[@]}"; do
ORIGINAL_LINE=$(( ENTRY_COUNT - idx ))
if [[ -n "$LINES_TO_DELETE" ]]; then
LINES_TO_DELETE="${LINES_TO_DELETE},${ORIGINAL_LINE}"
else
LINES_TO_DELETE="${ORIGINAL_LINE}"
fi
done
awk -v lines="$LINES_TO_DELETE" 'BEGIN{split(lines,a,","); for(i in a) skip[a[i]]=1} !(NR in skip)' "$HISTORY_FILE" > "$HISTORY_FILE.tmp"
mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
DELETE_COUNT=${#INDICES[@]}
osascript -e "display notification \"${DELETE_COUNT}件のエントリを削除しました\" with title \"Clipboard History\"" 2>/dev/null
}
# --- 設定モード ---
run_settings() {
while true; do
# 現在の設定を表示用に変換
get_status_text() {
if [[ "$1" == "true" ]]; then
echo "ON"
else
echo "OFF"
fi
}
PASTE_STATUS=$(get_status_text "$NOTIFY_ON_PASTE")
ERROR_STATUS=$(get_status_text "$NOTIFY_ON_ERROR")
# アクション文言を動的に生成
if [[ "$NOTIFY_ON_PASTE" == "true" ]]; then
PASTE_ACTION="ペースト通知をOFFにする"
else
PASTE_ACTION="ペースト通知をONにする"
fi
if [[ "$NOTIFY_ON_ERROR" == "true" ]]; then
ERROR_ACTION="エラー通知をOFFにする"
else
ERROR_ACTION="エラー通知をONにする"
fi
# 設定メニューを表示
SETTINGS_RESULT=$(osascript 2>/dev/null <<EOF
set currentSettings to "【現在の設定】
ペースト成功時: $PASTE_STATUS
エラー時: $ERROR_STATUS"
set actionList to {"$PASTE_ACTION", "$ERROR_ACTION", "戻る"}
set selectedAction to choose from list actionList with prompt currentSettings with title "Clipboard Manager - 通知設定"
if selectedAction is false then
return "CANCEL"
else
return item 1 of selectedAction
end if
EOF
) || echo "CANCEL"
# 「戻る」「キャンセル」の場合はループを抜ける
if [[ "$SETTINGS_RESULT" == "戻る" ]] || [[ "$SETTINGS_RESULT" == "CANCEL" ]]; then
return
fi
# ユーザーの選択に応じて設定を変更
case "$SETTINGS_RESULT" in
"ペースト通知をONにする")
NOTIFY_ON_PASTE=true
MESSAGE="ペースト通知: ON"
;;
"ペースト通知をOFFにする")
NOTIFY_ON_PASTE=false
MESSAGE="ペースト通知: OFF"
;;
"エラー通知をONにする")
NOTIFY_ON_ERROR=true
MESSAGE="エラー通知: ON"
;;
"エラー通知をOFFにする")
NOTIFY_ON_ERROR=false
MESSAGE="エラー通知: OFF"
;;
esac
# 設定ファイルに保存
cat > "$CONFIG_FILE" <<CONFIGEOF
# Clipboard Manager 設定ファイル
# ペースト時の通知を表示するか(true/false)
NOTIFY_ON_PASTE=$NOTIFY_ON_PASTE
# エラー時の通知を表示するか(true/false)
NOTIFY_ON_ERROR=$NOTIFY_ON_ERROR
CONFIGEOF
# 結果を通知
osascript -e "display notification \"$MESSAGE\" with title \"設定を保存しました\"" 2>/dev/null
# ループ継続(UIを再表示して最新の状態を見せる)
done
}
# --- メインモード ---
while true; do
check_history
ENTRY_COUNT=$(wc -l < "$HISTORY_FILE" | tr -d ' ')
build_preview_list
# 末尾に編集・設定オプション
AS_LIST="${AS_LIST}, \"--- クリップボード履歴を編集 ---\", \"--- 通知設定 ---\""
INDEX=$(osascript 2>/dev/null <<EOF
set historyList to {${AS_LIST}}
set selectedItem to choose from list historyList with prompt "クリップボードエントリを選択:" with title "Clipboard History" default items {item 1 of historyList}
if selectedItem is false then
return "-1"
end if
set selectedText to item 1 of selectedItem
repeat with i from 1 to count of historyList
if item i of historyList is selectedText then
return ((i - 1) as text)
end if
end repeat
return "-1"
EOF
) || true
[[ -z "$INDEX" || "$INDEX" == "-1" ]] && exit 0
# 編集モードが選択された場合
if (( INDEX == LIST_ITEM_COUNT )); then
run_edit
continue
fi
# 設定モードが選択された場合
if (( INDEX == LIST_ITEM_COUNT + 1 )); then
run_settings
continue
fi
# 通常選択:クリップボードに復元
break
done
# 通常選択の処理
ERROR_LOG="$HOME/.clipboard_pick_error.log"
JQ_RESULT=$(tail -r "$HISTORY_FILE" | /usr/bin/jq -s ".[$INDEX].content // empty" -r 2>>"$ERROR_LOG")
if [[ -z "$JQ_RESULT" ]]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] jq結果が空 INDEX=$INDEX ENTRY_COUNT=$ENTRY_COUNT" >> "$ERROR_LOG"
if [[ "$NOTIFY_ON_ERROR" == "true" ]]; then
osascript -e 'display notification "エラー: エントリ取得失敗。~/.clipboard_pick_error.log を確認" with title "Clipboard History"' 2>/dev/null
fi
exit 1
fi
# クリップボードに復元
if ! echo -n "$JQ_RESULT" | pbcopy 2>>"$ERROR_LOG"; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] pbcopy失敗" >> "$ERROR_LOG"
if [[ "$NOTIFY_ON_ERROR" == "true" ]]; then
osascript -e 'display notification "エラー: クリップボードへのコピー失敗" with title "Clipboard History"' 2>/dev/null
fi
exit 1
fi
# AppleScriptでcmd+vを送信
APPLESCRIPT_OUTPUT=$(osascript 2>&1 <<'APPLESCRIPT_EOF'
try
tell application "System Events"
delay 0.15
keystroke "v" using command down
end tell
return "SUCCESS"
on error errMsg number errNum
return "ERROR:" & errNum & ":" & errMsg
end try
APPLESCRIPT_EOF
)
APPLESCRIPT_EXIT_CODE=$?
# 結果に応じた通知
if [[ $APPLESCRIPT_EXIT_CODE -eq 0 ]] && [[ "$APPLESCRIPT_OUTPUT" == "SUCCESS" ]]; then
if [[ "$NOTIFY_ON_PASTE" == "true" ]]; then
osascript -e 'display notification "ペーストしました" with title "Clipboard History"' 2>/dev/null
fi
else
if [[ "$NOTIFY_ON_ERROR" == "true" ]]; then
osascript -e 'display notification "クリップボードに復元しました。⌘Vでペーストしてください(アクセシビリティ権限が必要な可能性があります)" with title "Clipboard History"' 2>/dev/null
fi
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment