初書 : 2021/05/26
mac : 11.2.3
zsh : 5.8
あらすじ
とある画像を連続で保存する。名前は「名称未設定.png」「名称未設定2.png」…(数十枚ほど)
ところが、ファイルを生成順に保存してしまったために、ファイル名の順番と、保存時間の順番が逆になってしまう。
mac / iphoneの写真アプリは、写真は時系列で並ぶため、このままだと逆順で並んでしまう・・・
前置き
というあらすじは置いといて、今回はファイルの作成時間を操作する方法を調べることになったのでメモ。
タイプスタンプを変更する
まずは一つのファイルだけ変更してみる。
これはとても簡単で、ファイル名を「名称未設定.png」とすると
% touch 202105261000 名称未設定.png
これで最終更新時間が2021年5月26日10時0分
に変更される。
ただ今回は秒単位で指定したいので、秒数も指定してみる。
% touch 202105261000.00 名称未設定.png
このように、.xx
をつけることで秒数まで指定することができる。(ミリ秒の設定方法は知らない)
・・・お気づきかもしれないが、touchで変更されるのはあくまで"最終更新時間"である。
そしてその際に、ファイルの生成時間が最終更新時間より前になった場合に限り、生成時間も上記時間に変更される。
ちなみに、写真アプリの時系列は更新時間ではなく生成時間。つまり、一度でも間違えて生成時間を必要以上に過去に戻すと、今度は本来過去に撮った写真達が混ざってしまうのである。
ファイル名を変更する
のちに必要になるので一応理解しておく。
これはファイルの移動であるmvを使うことで簡単に変更できる。
% mv 名称未設定.png 名称未設定1.png
これで名称未設定.png
が名称未設定1.png
になった。
複数のファイルを連続で操作する。
次にファイルを連続で変更してみる。今回は一旦全てのファイルの作成時間を2021年5月26日10時0分0秒
にしてみる。
% for f in 名称未設定*; do; touch -t 202105261000.00 $f; done
これで同ディレクトリにある「名称未設定」から始まるファイルの最終更新時間が全て2021年5月26日10時0分0秒
に置き換わった。
一応説明をしておくと、zshでループを回すのが、
for f in 名称未設定*; do;
done
これで名称未設定*
(*
はワイルドガード。0文字以上の任意の文字列)のファイル名を一つずつ変数f
に代入し、ループする
代入されたf
は$f
もしくは${f}
で呼び出すことが可能。
そのため、touch -t 202105261000.00 $f;
で$fにファイル名が入り、全ての変更時間が変わったという仕組み。
ファイル名を統一する
複数ファイルの最終更新時間はこれで変更できたのだが、一つ問題がある。
それは全てのファイルが同じ時間なので、写真アプリで順番が保証されなくなること。1
ということで、ファイル名毎に1文字ずつずらしてみる。
…の前に、先程のforループで、for文で取得できるファイル名を一度確認してみる。
% 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
仕方ないので、名称未設定1.png
を名称未設定01.png
という風に、先頭に0をつけることにする。
% for f in 名称未設定?.png; do; mv $f ${f/名称未設定/名称未設定0}; done
また少し見慣れないもの。${value/before/after}
は、変数value
の中に含まれているbefore
をafter
に変えるというもの。replace。
これで、名称未設定
を名称未設定0
に置き換えている。まぁ定
を定0
でも別にいいのだが・・・。
これで再度lsしてみると、番号順に並んでいるのがわかる。
ちなみに
zsh使っている場合は
% autoload -Uz zmv
% noglob zmv -W 名称未設定?.png 名称未設定0?.png
でもいけるらしい。
zsh の zmv を使って簡単に複数ファイルを一括リネームする - mollifier delta blog
ファイル名順に時間をずらす
改めて、時間をずらしていく。
今回は枚数が数十枚あるということもあり、分ではなく秒単位で変更していくことにする。
ちなみに60枚以上ある場合はフォルダ2つに分けて別々で実行するなり工夫するなりしないといけない(秒数は60秒なので)
% 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コマンドで確認する。
これで無事にタイムスタンプを書き換えることに成功した。
まとめ。
% 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
にでもする。
次に先程のコードを(多少加工しながら)貼り付ける。
# !/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
時間のところだけ別の変数で置いてみた。後で日付だけ書き換えやすくなる。
保存したら、次にファイルに実行権限を与える。これをしないと実行できない。
% chmod 755 change_timestamp.sh
できたら次に実際に実行してみる。
% ./change_timestamp.sh
これで次からもこのファイルだけでタイムスタンプを変更できるようになる。
終わりに
ここまでシェルスクリプト使うこと滅多になかったので、意外に苦戦した。
また何か作る機会あれば挑戦してみるかもしれない。
あとファイルの弱点として、for文に 該当するファイルが存在していない時、no matches found:
という風に出るので、この辺も上手く対応するプログラムにしたほうがいいのかなと悩みつつ・・・。
参考サイト:
【 touch 】コマンド――タイムスタンプを変更する/新規ファイルを作成する:Linux基本コマンドTips(23) - @IT
シェルスクリプト入門 書き方のまとめ
Linux — シェルの変数を四則演算で計算する (足し算・引き算・掛け算・割り算) - Qiita
シェルスクリプトで0埋め数字の処理を行うときの注意
dateコマンドで、ファイルのタイムスタンプを取得 | ex1-lab
【初心者向け】シェルスクリプトの作り方と実行方法