ファイルの作成時間(タイムスタンプ)を書き換えたい

初書 ： 2021/05/26
mac : 11.2.3
zsh ： 5.8

# あらすじ

とある画像を連続で保存する。名前は「名称未設定.png」「名称未設定2.png」…(数十枚ほど)
ところが、ファイルを生成順に保存してしまったために、ファイル名の順番と、保存時間の順番が逆になってしまう。
mac / iphoneの写真アプリは、写真は時系列で並ぶため、このままだと逆順で並んでしまう・・・

# 前置き

というあらすじは置いといて、今回はファイルの作成時間を操作する方法を調べることになったのでメモ。

# タイプスタンプを変更する

まずは一つのファイルだけ変更してみる。

これはとても簡単で、ファイル名を「名称未設定.png」とすると

```zsh
% touch 202105261000 名称未設定.png
```

これで最終更新時間が`2021年5月26日10時0分`に変更される。

ただ今回は秒単位で指定したいので、秒数も指定してみる。

```zsh
% touch 202105261000.00 名称未設定.png
```

このように、`.xx`をつけることで秒数まで指定することができる。(ミリ秒の設定方法は知らない)


・・・お気づきかもしれないが、touchで変更されるのはあくまで"最終更新時間"である。
そしてその際に、ファイルの生成時間が最終更新時間より前になった場合に限り、生成時間も上記時間に変更される。

ちなみに、写真アプリの時系列は更新時間ではなく生成時間。つまり、一度でも間違えて生成時間を必要以上に過去に戻すと、今度は本来過去に撮った写真達が混ざってしまうのである。

# ファイル名を変更する

のちに必要になるので一応理解しておく。
これはファイルの移動であるmvを使うことで簡単に変更できる。

```zsh
% mv 名称未設定.png 名称未設定1.png
```

これで`名称未設定.png`が`名称未設定1.png`になった。

# 複数のファイルを連続で操作する。

次にファイルを連続で変更してみる。今回は一旦全てのファイルの作成時間を`2021年5月26日10時0分0秒`にしてみる。

```zsh
% for f in 名称未設定*; do; touch -t 202105261000.00 $f; done
```

これで同ディレクトリにある「名称未設定」から始まるファイルの最終更新時間が全て`2021年5月26日10時0分0秒`に置き換わった。

一応説明をしておくと、zshでループを回すのが、

```zsh
for f in 名称未設定*; do;
done
```

これで`名称未設定*`（`*`はワイルドガード。0文字以上の任意の文字列）のファイル名を一つずつ変数`f`に代入し、ループする
代入された`f`は`$f`もしくは`${f}`で呼び出すことが可能。
そのため、`touch -t 202105261000.00 $f;`で$fにファイル名が入り、全ての変更時間が変わったという仕組み。

# ファイル名を統一する

複数ファイルの最終更新時間はこれで変更できたのだが、一つ問題がある。
それは全てのファイルが同じ時間なので、写真アプリで順番が保証されなくなること。[^1]

[^1]: 試してないのでどういう並び順になるのかは知らないが、念の為ずらした方が安心だよね

ということで、ファイル名毎に1文字ずつずらしてみる。

…の前に、先程のforループで、for文で取得できるファイル名を一度確認してみる。

```zsh
% for f in 名称未設定*; do; ls $f; done
名称未設定1.png
名称未設定10.png
名称未設定11.png
名称未設定12.png
名称未設定2.png
名称未設定3.png
名称未設定4.png
名称未設定5.png
名称未設定6.png
名称未設定7.png
名称未設定8.png
名称未設定9.png
```

はい。Finderでは番号順に並んでくれているが、シェルではそうはなってくれない。もしかしたら方法があるかもしれないが知らない。[^2]

[^2]: 書いている途中で気づいたが、ワイルドガードより番号のところを直接指定すればよかったのでは？

仕方ないので、`名称未設定1.png`を`名称未設定01.png`という風に、先頭に0をつけることにする。

```zsh
% for f in 名称未設定?.png; do; mv $f ${f/名称未設定/名称未設定0}; done
```

また少し見慣れないもの。`${value/before/after}`は、変数`value`の中に含まれている`before`を`after`に変えるというもの。replace。
これで、`名称未設定`を`名称未設定0`に置き換えている。まぁ`定`を`定0`でも別にいいのだが・・・。

これで再度lsしてみると、番号順に並んでいるのがわかる。

## ちなみに

zsh使っている場合は

```zsh
% autoload -Uz zmv
% noglob zmv -W 名称未設定?.png 名称未設定0?.png
```

でもいけるらしい。

[zsh の zmv を使って簡単に複数ファイルを一括リネームする - mollifier delta blog](https://mollifier.hatenablog.com/entry/20101227/p1)

# ファイル名順に時間をずらす

改めて、時間をずらしていく。
今回は枚数が数十枚あるということもあり、分ではなく秒単位で変更していくことにする。

ちなみに60枚以上ある場合はフォルダ２つに分けて別々で実行するなり工夫するなりしないといけない（秒数は60秒なので）

```zsh
% i=0; for f in 名称未設定*; do; touch -t 202105261000.$(printf "%02d" ${i}) $f; o=$((++i)); done
```

一応解説。
最初の`i=0`は変数代入。jsとかの`var i = 0;`と同じ。ただ空白は空けれない。
次の`$(printf "%02d" ${i})`は変数iを0埋めの2桁で出力するため。これをしないと`xx.1`とか`xx.2`とかになってしまい、秒数が認識されなくなる。
その次の`o=$((++i))`はインクリメント。変数`o`に代入しているのはwarning出さないため。(より良い方法あるのだろうか)

これで、ファイル名毎に1秒ずれたファイルたちが完成した。
ちなみにfinderでは秒数まで確認できないので、確認する場合は`% date -r 名称未設定02.txt`などのdateコマンドで確認する。

これで無事にタイムスタンプを書き換えることに成功した。

# まとめ。

```zsh
% for f in 名称未設定?.png; do; mv $f ${f/名称未設定/名称未設定0}; done
% i=0; for f in 名称未設定*; do; touch -t 202105261000.$(printf "%02d" ${i}) $f; o=$((++i)); done
```

ファイル名を変更し、時間をずらす。これだけである。

# せっかくなのでファイル化する。

毎回入力するのもあれだしシェルスクリプトとして保存しておこう、という話。
1回しか使わないならこれ以降は特に見なくてもいいやつ・・・かも。

まずはファイル作成。名前は適当に`change_timestamp.sh`にでもする。

次に先程のコードを（多少加工しながら）貼り付ける。

```zsh:change_timestamp.sh
#!/bin/zsh

# 年月日時分の12桁
timestamp=202105261000

# ファイル名を操作する
for f in 名称未設定?.png; do;
    mv $f ${f/名称未設定/名称未設定0};
done

# タイムスタンプをずらす
i=0;
for f in 名称未設定*; do;
    touch -t $timestamp.$(printf "%02d" ${i}) $f;
    o=$((++i));
done
```

時間のところだけ別の変数で置いてみた。後で日付だけ書き換えやすくなる。

保存したら、次にファイルに実行権限を与える。これをしないと実行できない。

```zsh
% chmod 755 change_timestamp.sh
```

できたら次に実際に実行してみる。

```zsh
% ./change_timestamp.sh
```

これで次からもこのファイルだけでタイムスタンプを変更できるようになる。

# 終わりに

ここまでシェルスクリプト使うこと滅多になかったので、意外に苦戦した。
また何か作る機会あれば挑戦してみるかもしれない。

あとファイルの弱点として、for文に 該当するファイルが存在していない時、`no matches found:`という風に出るので、この辺も上手く対応するプログラムにしたほうがいいのかなと悩みつつ・・・。

# 参考サイト：

[【 touch 】コマンド――タイムスタンプを変更する／新規ファイルを作成する：Linux基本コマンドTips（23） - ＠IT](https://www.atmarkit.co.jp/ait/articles/1606/14/news013.html)
[シェルスクリプト入門 書き方のまとめ](http://motw.mods.jp/shellscript/tutorial.html#substitution)
[Linux — シェルの変数を四則演算で計算する (足し算・引き算・掛け算・割り算) - Qiita](https://qiita.com/YumaInaura/items/d1c477993403b87fb210)
[シェルスクリプトで0埋め数字の処理を行うときの注意](https://rcmdnk.com/blog/2019/09/09/computer-bash-zsh/)
[dateコマンドで、ファイルのタイムスタンプを取得 | ex1-lab](https://ex1.m-yabe.com/archives/4311)
[【初心者向け】シェルスクリプトの作り方と実行方法](https://eng-entrance.com/linux-shellscript-do)