- macOSでファイル名の先頭に作成日時を追加するBashスクリプトを作成しました。
- このスクリプトは実際のファイル作成時刻を取得し、既にタイムスタンプがついたファイルは処理をスキップします。
- 環境依存はありますが、時系列での文書管理がより簡単になる実用的なツールです。
![[macOS]ファイル名の先頭に作成日時を追加するBashスクリプトの作り方](https://chiilabo.com/wp-content/uploads/2020/09/instructor-m.png)
生成AIを使うとスクリプトはすぐに出来上がります。
しかし、実際に使うと細かな注意点(境界条件)が出てきます。
スクリプト内のコマンドの特徴を知っておくと、補足して仕上げるのがスムーズです。
#!/bin/bash
# 複数ファイル対応のファイル名先頭タイムスタンプ付与スクリプト(macOS用)
# 引数チェック
if [ $# -eq 0 ]; then
echo "使用方法: $0 <ファイルパス> [ファイルパス...]"
echo "例: $0 *.md または $0 file1.txt file2.doc"
exit 1
fi
# 各ファイルに対して処理を実行
for FILE_PATH in "$@"; do
# ファイルの存在確認
if [ ! -f "$FILE_PATH" ]; then
echo "スキップ: '$FILE_PATH' はファイルではありません"
continue
fi
# ファイル名とディレクトリを取得
DIR=$(dirname "$FILE_PATH")
FILENAME=$(basename "$FILE_PATH")
# ファイル名の先頭文字を確認
FIRST_CHAR=$(echo "$FILENAME" | cut -c1)
# 先頭が数字または記号の場合はスキップ
if [[ "$FIRST_CHAR" =~ [0-9\!\@\#\$\%\^\&\*\(\)\_\+\-\=\[\]\{\}\;\:\"\'\,\.\/\<\>\?] ]]; then
echo "無視: $FILENAME"
continue
fi
# ファイル作成時刻を取得してタイムスタンプ形式に変換
CREATION_TIME=$(stat -f %B "$FILE_PATH")
TIMESTAMP=$(date -r "$CREATION_TIME" +"%Y-%m-%d-%H%M")
# 新しいファイル名を生成
NEW_FILENAME="${TIMESTAMP}_${FILENAME}"
# 同名ファイルが存在する場合の処理(連番付与)
FINAL_FILENAME="${NEW_FILENAME}"
COUNT=1
while [ -e "${DIR}/${FINAL_FILENAME}" ]; do
FINAL_FILENAME="${TIMESTAMP}_${COUNT}_${FILENAME}"
COUNT=$((COUNT+1))
done
# ファイル名変更を実行
mv "$FILE_PATH" "${DIR}/${FINAL_FILENAME}"
# 元のファイル名と新しいファイル名を表示
if [ "$FINAL_FILENAME" = "$NEW_FILENAME" ]; then
echo "変更: $FILENAME → $FINAL_FILENAME"
else
echo "変更: $FILENAME → $FINAL_FILENAME (連番付与)"
fi
done
# 処理完了メッセージ
echo "処理が完了しました"
1. 文書管理でファイル名の先頭に作成日時を付けたい
プロジェクトの文書管理でファイル名の先頭に作成日時を付けたいケースがあります。
ファイルの実際の作成日時を取得し、ファイル名の先頭にYYYY-MM-DD-hhmm_
形式でタイムスタンプを追加するシンプルなBashスクリプトを作りました。
環境:macOS Sonoma 14.7

使用方法:
bash timestamp-script.sh /path/to/your/file.doc

#!/bin/bash
# 複数ファイル対応のファイル名先頭タイムスタンプ付与スクリプト(macOS用)
# 引数チェック
if [ $# -eq 0 ]; then
echo "使用方法: $0 <ファイルパス> [ファイルパス...]"
echo "例: $0 *.md または $0 file1.txt file2.doc"
exit 1
fi
# 各ファイルに対して処理を実行
for FILE_PATH in "$@"; do
# ファイルの存在確認
if [ ! -f "$FILE_PATH" ]; then
echo "スキップ: '$FILE_PATH' はファイルではありません"
continue
fi
# ファイル名とディレクトリを取得
DIR=$(dirname "$FILE_PATH")
FILENAME=$(basename "$FILE_PATH")
# ファイル名の先頭文字を確認
FIRST_CHAR=$(echo "$FILENAME" | cut -c1)
# 先頭が数字または記号の場合はスキップ
if [[ "$FIRST_CHAR" =~ [0-9\!\@\#\$\%\^\&\*\(\)\_\+\-\=\[\]\{\}\;\:\"\'\,\.\/\<\>\?] ]]; then
echo "無視: $FILENAME"
continue
fi
# ファイル作成時刻を取得してタイムスタンプ形式に変換
CREATION_TIME=$(stat -f %B "$FILE_PATH")
TIMESTAMP=$(date -r "$CREATION_TIME" +"%Y-%m-%d-%H%M")
# 新しいファイル名を生成して変更
NEW_FILENAME="${TIMESTAMP}_${FILENAME}"
mv "$FILE_PATH" "${DIR}/${NEW_FILENAME}"
echo "変更: $FILENAME → $NEW_FILENAME"
done
# 処理完了メッセージ
echo "処理が完了しました"
1-1. スクリプトの処理の流れ
スクリプトでファイル名の先頭にタイムスタンプを追加には:
- 指定されたファイルパスからファイル名を取得します
- ファイル名の先頭文字をチェックし、数字または記号で始まる場合は処理をスキップします
- ファイルの作成日時からタイムスタンプ(YYYY-MM-DD-hhmm形式)を生成します
- 元のファイル名の前にタイムスタンプを追加します
- ファイル名を変更し、結果を表示します
ただし、既にタイムスタンプが付いているファイル(数字や記号で始まるファイル名)は処理されず、スキップされます。

完成したスクリプトの簡易版から、エッセンスを概観します。
#!/bin/bash
# 基本的なファイル名先頭タイムスタンプ付与スクリプト(macOS用)
# 引数からファイルパスを取得
FILE_PATH="$1"
# ファイル名とディレクトリを取得
DIR=$(dirname "$FILE_PATH")
FILENAME=$(basename "$FILE_PATH")
# ファイル名の先頭文字を確認
FIRST_CHAR=$(echo "$FILENAME" | cut -c1)
# ファイル作成時刻を取得してタイムスタンプ形式に変換
CREATION_TIME=$(stat -f %B "$FILE_PATH")
TIMESTAMP=$(date -r "$CREATION_TIME" +"%Y-%m-%d-%H%M")
# 新しいファイル名を生成して変更
NEW_FILENAME="${TIMESTAMP}_${FILENAME}"
mv "$FILE_PATH" "${DIR}/${NEW_FILENAME}"
echo "変更: $FILENAME → $NEW_FILENAME"

完成コードから、細かいエラー処理や説明を省略したものです。
2. 工夫したポイント
気付いた問題ごとに
- ファイル作成日時を取得する
- 二重のタイムスタンプ付与を防ぐ
- ワイルドカードで複数のファイルを一括処理できる
- 同名ファイルになる場合に上書きしない(連番処理)

やりたいことはファイル名の変更でも、プログラムにするときには細部を詰める必要があるんだね。
2-1. ファイル作成日時を使用(stat -f %B)
このスクリプトでは、ファイルの実際の作成日時を使用しています。
システムの現在時刻ではなく、コマンド「stat -f %B
」を使って、ファイルの作成時刻を取得しています。
# 作成時刻をエポック秒で取得し、指定フォーマットに変換
CREATION_TIME=$(stat -f %B "$FILE_PATH")
TIMESTAMP=$(date -r "$CREATION_TIME" +"%Y-%m-%d-%H%M")
ただし、作成時刻の取得はmacOSのコマンドに依存しています。
Linuxでは作成時刻の取得方法は異なり、採用しているファイルシステムによっては対応していない場合もあります。
例えば、Ubuntuで同じことをするには、stat
コマンドの引数を変える必要があります(stat -c %W "$FILE_PATH"
)。
# Ubuntuでのファイル作成時刻取得(ext4ファイルシステムの場合)
# 注: 古いext2/3ファイルシステムでは作成時刻が保存されていない場合があります
CREATION_TIME=$(stat -c %W "$FILE_PATH")
# 作成時刻が取得できない場合(0が返る)は変更時刻を使用
if [ "$CREATION_TIME" = "0" ]; then
echo "警告: 作成時刻が取得できないため、変更時刻を使用します"
CREATION_TIME=$(stat -c %Y "$FILE_PATH")
fi
# タイムスタンプ形式に変換
TIMESTAMP=$(date -d "@$CREATION_TIME" +"%Y-%m-%d-%H%M")
#!/bin/bash
# 使用方法の表示
function show_usage {
echo "使用方法: $0 <ファイルパス>"
echo "指定されたファイルの名前の先頭にファイル作成日時のタイムスタンプを追加します。"
echo "タイムスタンプ形式: YYYY-MM-DD-hhmm_"
echo "既にタイムスタンプが付いていると思われるファイル(先頭が数字や記号)は無視されます。"
}
# 引数チェック
if [ $# -ne 1 ]; then
show_usage
exit 1
fi
FILE_PATH="$1"
# ファイルの存在確認
if [ ! -f "$FILE_PATH" ]; then
echo "エラー: ファイル '$FILE_PATH' が見つかりません。"
exit 1
fi
# ファイル名とディレクトリを取得
DIR=$(dirname "$FILE_PATH")
FILENAME=$(basename "$FILE_PATH")
# ファイル名の先頭文字を確認
FIRST_CHAR=$(echo "$FILENAME" | cut -c1)
# 先頭が数字(0-9)または記号(#.-_など)の場合は処理をスキップ
if [[ "$FIRST_CHAR" =~ [0-9\!\@\#\$\%\^\&\*\(\)\_\+\-\=\[\]\{\}\;\:\"\'\,\.\/\<\>\?] ]]; then
echo "無視: ファイル '$FILENAME' は既にタイムスタンプが付いているか、先頭が数字/記号です。"
exit 0
fi
# OSの種類によって作成日時の取得方法を変える
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOSの場合、stat -f %Bで作成時刻を取得
CREATION_TIME=$(stat -f %B "$FILE_PATH")
TIMESTAMP=$(date -r "$CREATION_TIME" +"%Y-%m-%d-%H%M")
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
# Linuxの場合
# 注: Linuxでは厳密な「作成時刻」が取得できないため、最も古い時刻を使用
# 多くのファイルシステムでは birth time (crtime) をサポートしていない
# statコマンドのバージョンを確認
if stat --version 2>&1 | grep -q 'GNU coreutils'; then
# GNU stat - birth time が使える場合(新しいファイルシステムとカーネル)
if stat -c %W "$FILE_PATH" | grep -qv '^0$'; then
# birth time がサポートされている
TIMESTAMP=$(stat -c %y "$FILE_PATH" | awk '{print $1"-"substr($2,1,5)}' | sed 's/://g' | sed 's/-//g' | sed 's/\(..\)\(..\)\(..\)\(..\)\(..\)/\1-\2-\3-\4\5/')
else
# birth time が使えない場合は変更時刻を使用
TIMESTAMP=$(stat -c %y "$FILE_PATH" | awk '{print $1"-"substr($2,1,5)}' | sed 's/://g' | sed 's/-//g' | sed 's/\(..\)\(..\)\(..\)\(..\)\(..\)/\1-\2-\3-\4\5/')
fi
else
# BSD like stat
TIMESTAMP=$(stat -c %y "$FILE_PATH" 2>/dev/null || stat -f "%Sm" -t "%Y-%m-%d-%H%M" "$FILE_PATH")
fi
else
# その他のOSでは現在時刻を使用
echo "警告: このOSではファイル作成日時の取得方法が不明です。現在時刻を使用します。"
TIMESTAMP=$(date +"%Y-%m-%d-%H%M")
fi
# 新しいファイル名
NEW_FILENAME="${TIMESTAMP}_${FILENAME}"
NEW_FILE_PATH="${DIR}/${NEW_FILENAME}"
# ファイル名変更
mv "$FILE_PATH" "$NEW_FILE_PATH"
# 結果表示
if [ $? -eq 0 ]; then
echo "成功: '$FILENAME' → '$NEW_FILENAME'"
else
echo "エラー: ファイル名の変更に失敗しました。"
exit 1
fi
2-2. 二重タイムスタンプ付与の防止
ちょっとしたことですが、既にタイムスタンプが付いているファイルに再度タイムスタンプを付けることを防ぐ工夫をしています。
ファイル名の先頭文字が数字や記号の場合、処理をスキップします。
# 先頭文字が数字や記号ならスキップ
if [[ "$FIRST_CHAR" =~ [0-9\!\@\#\$\%\^\&\*\(\)\_\+\-\=\[\]\{\}\;\:\"\'\,\.\/\<\>\?] ]]; then
echo "無視: $FILENAME"
exit 0
fi

記号は、タイムスタンプを付けない目印にもできるんですね。
2-3. 複数のファイルをまとめて受け取れる(*.*)
$1
で1つ目の引数だけを処理していたのを、"$@"
を使って全ての引数を処理するようにしました。
for ループを使って各ファイルを順番に処理します
# 各ファイルに対して処理を実行
for FILE_PATH in "$@"; do
# ファイルごとの処理
done
これにより、ワイルドカードを利用した一括処理が可能です。
bash timestamp-script.sh /path/to/your-document-folder/*.doc

今回は、フォルダを受け取って、内部のファイルをまとめて処理するようにはしていません。
フォルダ名を変えることもあるからです。
2-4. 同名ファイルが存在したときは連番を付ける
ファイル名変更(mv
コマンド)で同名のファイルが存在する場合、デフォルトの動作では警告なしに上書きされます。
現在のスクリプトでは、新しいファイル名(タイムスタンプを付与したもの)と同じ名前のファイルが既に存在する場合、そのファイルは警告なしに上書きされてしまいます。
そこで、連番を付けるなど、重複しないファイル名を生成する必要があります。
FINAL_FILENAME="${NEW_FILENAME}"
COUNT=1
while [ -e "${DIR}/${FINAL_FILENAME}" ]; do
FINAL_FILENAME="${TIMESTAMP}_${COUNT}_${FILENAME}"
COUNT=$((COUNT+1))
done
mv "$FILE_PATH" "${DIR}/${FINAL_FILENAME}"
3. 生成AIと段階的に作っていった(Claude 3.7 Sonnetの場合)
以下のようなプロンプトを与えると、工夫ポイントを反映したスクリプトを作成できます。
ファイル名の先頭に作成日時のタイムスタンプを追加するBashスクリプトを作成してください。
例:document.doc
→ 2025-03-23-1001_document.doc
- YYYY-MM-DD-hhmm_[filename]形式
- 先頭が数字や記号の場合は無視(二重追加防止)
- macOS Sonoma用
- ファイルの実際の作成日時を使用(現在時刻じゃなく)
- ワイルドカード対応(
*.md
などで複数ファイル処理) - 同名ファイルになる場合は連番付与して上書き防止
ただし、これらの工夫ポイントは、はじめからわかっていたわけではありません。
段階的に、スクリプトを作っていく途中で問題に気づけるからです。


初期のプロンプトからどのように改善していったのか、やり取りの流れをかいつまんで見てみましょう。
3-1. シーン1: 基本機能の要件定義
はじめに与えたプロンプトは、「ただの思いつき」でした。
bashのスクリプトでファイル名の先頭にタイムスタンプを追加するスクリプトを考えてください。
例 timeblock-spec.doc → 2025-03-23-1001_timeblock-spec.doc
YYYY-MM-DD-hhmm_[filename]の形式です。
ただし、ファイル名の先頭文字が半角数字・半角記号の場合は無視するようにします。
(二重に付加しないため) ファイルパスを与えると、そのファイル名を変更するスクリプトを考えます。
このようなアイディアを元に、詳細を詰めていきます。
これは、クライアントとプログラマーのやり取りに似ています。
- クライアント: ファイル名の先頭にタイムスタンプを追加するbashスクリプトが必要なんだ。たとえば「timeblock-spec.doc」というファイルがあったら、「2025-03-23-1001_timeblock-spec.doc」という形式に変換したい。
- プログラマー: なるほど、YYYY-MM-DD-hhmm_[ファイル名] の形式ですね。
- クライアント: そう、ちょうどそんな感じ。あと、ファイル名の先頭が既に半角数字や半角記号で始まっている場合は無視してほしい。タイムスタンプが二重についちゃうのを防ぎたいんだ。
- プログラマー: 分かりました。ファイルパスを引数として受け取って、そのファイル名を変更するスクリプトを作成しますね。
- クライアント: ちなみに、OSはmacOSで使うよ。Sonomaバージョンだね。
- プログラマー: macOS Sonoma向けのスクリプトですね。了解しました。macOSのコマンドを使って作成します。
3-2. シーン2: 複数ファイル対応の依頼
できたスクリプトを実際に使うと、不便な点に気づきます。
- クライアント: スクリプトを「bash timestamp-script.sh *.md」で実行してみたんだけど、パラメータエラーになっちゃったよ。複数のファイル名を一度に処理できるようにしてもらえないかな?
- プログラマー: すみません、現在のスクリプトは単一ファイル用になっていますね。複数のファイル名を受け取って処理できるように修正します。
3-3. シーン3: 同名ファイル処理の確認と改善
さらに使っていると、疑問点が湧いてきます。
- クライアント: あとひとつ気になるんだけど、もし変換後のファイル名と同じ名前のファイルが既に存在していた場合、ファイルの上書きってどうなるの?
- プログラマー: 良い質問ですね。現在のスクリプトでは、同名ファイルがある場合は警告なしに上書きされてしまいます。安全のために、同名ファイルがある場合は処理をスキップするか、別の名前で保存するようにしたほうがいいですね。
- クライアント: そうだね、同名ファイルがあったらスキップするか、ユーザーに確認を求めるようにしてほしいな。データが失われるのは避けたいよ。
- プログラマー: 承知しました。同名ファイルがある場合は処理をスキップして警告メッセージを表示するように修正します。必要であれば、対話式で上書きの確認を求めることもできますが、いかがしましょうか?
- クライアント: 連番処理があると便利だね。
- プログラマー: 分かりました。これで安全にファイル名変更が行えるようになりますね。

このようなやり取りを経て、ファイル整理のためのシンプルながら実用的なスクリプトを作りました。
ファイルの作成日時からファイル名に付加すると、時系列での文書管理がより簡単になります。
![[macOS]シェルスクリプトをFinderから実行するときの注意点(作業フォルダ)](https://chiilabo.com/wp-content/uploads/2024/12/image-9-47-1024x576.jpg)

![[macOS] ファイル名を一括変更するターミナルコマンド(sedでループ)](https://chiilabo.com/wp-content/uploads/2024/02/image-15-1024x576.jpg)

![[mac] コマンドラインからゴミ箱に入れる【~/.Trash】](https://chiilabo.com/wp-content/uploads/2022/04/image-11-1024x576.png)
![[Mac] サブフォルダ内のファイルを親フォルダに集める【フラット化のコマンド】](https://chiilabo.com/wp-content/uploads/2021/12/ScreenShot-2021-12-18-11.52.47-1024x575.png)