junkerstock
 command作成 t-count

t-count というコマンドラインで動くコマンドを作りたい。 
使用方法としては、t-count 英語数字で10文字  とする。 英語数字で10文字 をラベルとする。
 どこか、Windowsのテンポラリが入るようなフォルダに t-count-tmp.txt というファイルをつくり、
この中に、ラベル:動かした時間 改行 が入る。次回動かした時、 同じラベルで動かした時は、
この t-count-tmp.txt から前に動かした時の時間をとり、差分の時間を 画面に報告する。
 例えばt-count aaa 実行(何も画面に出ない) 10分後  t-count aaa 実行で 「aaa 起動時間は 0時間10分0秒です」 とでる。
 複数のバッチで別のt-count コマンドを使った時、 t-count-tmp.txt がアクセスされていてバッティングする可能性がある為、
フックし、待つ部分も入れてほしい。 これは、起動時間を測定するだけのコマンドだ。




1. Go本体のインストール
まずは公式サイトからインストーラーをダウンロードします。

公式サイト: go.dev/dl

手順: Windows用の .msi ファイルを実行し、基本はすべて「Next」でOKです。

確認: コマンドプロンプトやPowerShellを開き、以下のコマンドを入力してバージョンが表示されれば成功です。

Bash
go version


2. エディタの設定(VS Code推奨)
Goの開発には VS Code が最も一般的で、プラグインが非常に強力です。

VS Codeはインストール済みとします。

Go拡張機能を導入: 拡張機能マーケットプレイスで「Go」(作成元: Go Team at Google)をインストールします。

・拡張機能のインストール手順
VS Code を開き、左側のバーにある四角いアイコン(Extensions / 拡張機能)をクリックします。

検索窓に 「Go」 と入力してください。

Go Team at Google が作成した、一番上に表示されるものを選択して [Install] をクリックします。

重要:依存ツールの「Install All」
拡張機能を入れただけでは、実は「最強の状態」にはなりません。Goにはフォーマット(整形)やコード解析を行う小さなツール群が多数あり、それを一括でインストールする必要があります。

Ctrl + Shift + P (Macは Cmd + Shift + P)を押して、コマンドパレットを開きます。

Go: Install/Update Tools と入力して選択します。

チェックボックスが一覧で出てくるので、すべてにチェックを入れて [OK] を押します。

これにより、gopls (補完), staticcheck (静的解析), dlv (デバッグ) などがインストールされます。


・この拡張機能でできるようになること
これを導入すると、コマンドラインアプリ開発が劇的に楽になります。

保存時に自動整形: Ctrl + S で保存するたびに、Goの標準規約(gofmt)に従ってコードが綺麗に整列されます。

型情報の表示: 変数にマウスを合わせるだけで、その変数が何の型か、どんなメソッドが使えるかを表示します。

定義へジャンプ: F12 を押すと、その関数が定義されている場所(標準ライブラリの中身まで!)へ一瞬で飛べます。

デバッグ実行: F5 キーで、一行ずつコードを実行しながらバグを探せます。





1. プロジェクトフォルダの準備
まずはプログラムを置く場所を作ります。

デスクトップなど好きな場所に t-count というフォルダを作ります。

VS Codeでそのフォルダを開きます(フォルダを右クリックして「Codeで開く」など)。

VS Codeのターミナル(Ctrl + @)を開き、以下のコマンドを打ちます。

Bash
go mod init t-count

これで go.mod という管理ファイルが作成されます。


2. コードを書く
VS Codeで新しいファイルを作成し、名前を main.go にします。

以下にコード記載有り 必ずCtrl+Sで保存する事  ファイル名の横の「●」マークが「×」に変われば保存完了


3. 動作確認(実行)
ビルドする前に、まずは正しく動くか試してみましょう。ターミナルで以下を打ちます。

Bash
go run main.go



4. EXEファイルにビルドする
いよいよ本題の「単独ファイル化」です。

基本のビルド
ターミナルで以下のコマンドを実行します。

Bash
go build -o t-count.exe
すると、フォルダの中に t-count.exe というファイルが出現します!




※リリース用ビルド
go build -ldflags="-s -w" -o t-count.exe




main.go--------------------

package main

import (
"bufio"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"
)

// 設定定数
const (
dataFileName = "t-count-tmp.txt"
lockFileName = "t-count-tmp.lock"
maxLabelLen = 10 // 最大文字数
)

func main() {
// 1. 引数チェック
if len(os.Args) < 2 {
// 引数がない場合は何もせず終了(エラーメッセージも出さないほうがバッチで使いやすい場合が多いが、念のため出すなら以下)
fmt.Println("Usage: t-count [label]")
os.Exit(1)
}
label := os.Args[1]

// 2. ラベルのバリデーション(10文字以内 & 英数字のみ)
if len(label) > maxLabelLen {
fmt.Printf("エラー: ラベルは%d文字以内で指定してください(現在: %d文字)\n", maxLabelLen, len(label))
os.Exit(1)
}
match, _ := regexp.MatchString("^[a-zA-Z0-9]+$", label)
if !match {
fmt.Println("エラー: ラベルは半角英数字のみ使用可能です。")
os.Exit(1)
}

// 3. パスの設定
tmpDir := os.TempDir()
dataPath := filepath.Join(tmpDir, dataFileName)
lockPath := filepath.Join(tmpDir, lockFileName)

// 4. ファイルロック(排他制御)
for {
f, err := os.OpenFile(lockPath, os.O_RDONLY|os.O_CREATE|os.O_EXCL, 0666)
if err == nil {
f.Close()
break
}
time.Sleep(50 * time.Millisecond)
}
// 終了時にロック解除
defer os.Remove(lockPath)

// 5. データファイルの読み込み
records := make(map[string]time.Time)

// ファイルが存在する場合のみ読み込む
if _, err := os.Stat(dataPath); err == nil {
file, err := os.Open(dataPath)
if err == nil {
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
// 【重要修正】ここで ":" で区切る際、最初の1つ目だけを区切るように修正
// 時間フォーマット(HH:MM:SS)にも ":" が含まれるため
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
t, err := time.Parse(time.RFC3339, parts[1])
if err == nil {
records[parts[0]] = t
}
}
}
file.Close()
}
}

// 6. ロジック判定(開始 or 終了)
startTime, exists := records[label]

if !exists {
// --- 開始処理 ---
// 記録がないので開始時刻を保存
records[label] = time.Now()
// 画面には何も出さない
} else {
// --- 終了処理 ---
// 記録があるので経過時間を計算
duration := time.Since(startTime)

// 時間表示の計算
totalSeconds := int(duration.Seconds())
hours := totalSeconds / 3600
minutes := (totalSeconds % 3600) / 60
seconds := totalSeconds % 60

fmt.Printf("%s 起動時間は %d時間%d分%d秒です\n", label, hours, minutes, seconds)

// 完了したのでマップから削除
delete(records, label)
}

// 7. データファイルへの書き込み(全書き換え)
outFile, err := os.Create(dataPath)
if err != nil {
// ここでエラーが出ると致命的なので表示する
fmt.Printf("Error writing file: %v\n", err)
os.Exit(1)
}
defer outFile.Close()

for k, v := range records {
line := fmt.Sprintf("%s:%s\n", k, v.Format(time.RFC3339))
outFile.WriteString(line)
}
}






Content-type: text/html error-smemo8

ERROR !

ファイルの差し替えに失敗しました: ./smemo8.log