お知らせ

  • 利用規約を守って投稿してください。また、よくある質問および投稿の手引きも参照してください。
  • メッセージの投稿にはアカウントが必要です。未登録の方は、ユーザ登録ページからアカウントを作成することができます。

#1 2016-12-21 14:53:31

koi
新しいメンバ
登録日: 2016-12-21

ファイルの一括リネームについて

こちらで質問していいのかわかりませんがお知恵を貸してください。
incronでディレクトリを監視して、該当ディレクトリにファイルが入った時にファイルのリネームを実行したいと考えています。
リネームのファイル名を YYYYmmddHHIIMMSSとしさらに末尾に各秒ごとに連番を振りたいと考えています。

201612211451220001.txt
201612211451220002.txt
201612211451220003.txt
201612211451230001.txt
201612211451230002.txt
...

このような処理をしたいのですがどのようにすれば実現できるのか見当がつきません。
何かヒントでもいただけると助かります。
よろしくお願い致します。

オフライン

 

#2 2016-12-21 22:20:13

yutarine
メンバ
登録日: 2011-10-15

Re: ファイルの一括リネームについて

incronについてはよく分からないので、リネーム処理に関する部分だけ考えてみます。

koiさんの実現したい処理を工程に分けてみると、
「現在の日時を取得する方法」、「連番を実現する方法」、「連番を所定の書式にする方法」、「取得した日時と連番をファイル名に反映させる方法」、「リネームを一括処理する方法」とに分割してそれぞれ考えるといいかと思います。
また処理後はリネームファイルとオリジナルファイルを他のディレクトリに隔離し、常に作業ディレクトリには新規ファイルのみが存在するようにすれば処理が簡素化できると思います。

それと日時の所得を随時行う処理にすると、21時51分02秒に処理を実行した場合(%H%M%S-連番のような書式)
215102-001.txt
215102-002.txt
215102-003.txt
215103-004.txt
215103-005.txt
のように処理の途中で02秒→03秒に変わると、連番と時刻の規則性がおかしくなってしまうのでそこのところも工夫する必要がありそうです。

オフライン

 

#3 2016-12-23 08:33:33

kznj
メンバ
登録日: 2013-12-03

Re: ファイルの一括リネームについて

少し気になる点はあるのですが、概ね実現できると思います。

以下の3点について説明します。
A) incron上の記述の仕方
B) リネーム処理の実装
C) 考慮すべき点


A)について

「該当ディレクトリにファイルが入った時」と言われているのでincrontabの書き方はこんな感じでしょうか?

# 対象ディレクトリ マスク コマンド
#  <path> <mask> <command>
/foo/bar  IN_CLOSE_WRITE  <コマンド> $@/$#

/foo/barディレクトリを監視して、そこに書き出されたファイルがクローズされると
そのファイルのフルパス名(対象ディレクトリ名$@+ファイル名$#)をコマンドに渡します。

参考:http://inotify.aiken.cz?section=incron&page=doc&lang=en

incron documentation
Example: You need to run program 'abc' with the full file path as an argument every time a file is changed in /var/mail. One of the solutions follows:

/var/mail IN_CLOSE_WRITE abc $@/$#

<mask>はIN_CREATEでもいいのでしょうか?
incronの挙動に詳しくないのでその点の判断はおまかせします。

それとリネームを後でしますからそれに該当してしまう<mask>の値は指定できません。
指定すると自分のリネーム処理で再度incronから呼び出されてしまいます。

B)について

後ほどc)で述べることが克服できれば
<コマンド>はbashのスクリプトで実装可能だと思います。

方針として次のようにしてみました。
・リネームするファイル名は元のファイルの作成日付か最終更新日付を元にする。
・連番は先に出来ているYYYYMMDDhhmmss+連番.txtの状態を調べて決める。

A)で述べたように作られたファイルのフルパス名がコマンドに渡ってきますから
それに対して次のような処理をします。

1) 対象ファイルに対するYYYYMMDDhhmmss(以下タイムスタンプ)を決定する
  → 渡ってきたファイル名に対してstatコマンドで
    Time of file birth(作成日付)か
    Time of last modification(最終更新日付)を取得する。
  → 取得結果は"YYYY-MM-DD hh:mm:ss.nnnnnnnnnn +0x00"のような形式なので
    ミリセカンド部や"-"、":"、スペースを除去して想定するタイムスタンプ形式にする。

2) 連番を決定する
  → 連番の初期値は0  ※ 1から始めるなら1にする。
  → タイムスタンプ+連番0のファイルの存在を確認して
    存在したら一番大きい連番のファイルを取得してその連番を切り出し
    その値に+1する。

3) 対象ファイルをリネームする
  → リネーム元ファイルはコマンドに渡ってきたファイル名
    リネーム先ファイル名は、タイムスタンプ+2)で決定した連番.txt
    (mv コマンドに渡ってきたファイル名 タイムスタンプ+2)で決定した連番.txt)

ちょっと書いてみたスクリプトはこんな感じです。
(タイムスタンプと連番の間に"-"が入るようになっています)

コード:

#!/bin/bash

TARGET=$1
DIR=`dirname ${TARGET}`

TM=`stat -c %y ${TARGET}`  #  %y:Time of last modification, human-readable

# 2016-12-22 06:16:51.607719000 +0900
TM=${TM%%.*} # remove mili-seconds
TM=${TM//:/} # remove ":"
TM=${TM//-/} # remove "-"
TM=${TM// /} # remve space

BRANCH=0
FILE_BASE=${DIR}/${TM}-0000.txt
FILE=${DIR}/${TM}-[0-9][0-9][0-9][0-9].txt

if [ -e ${FILE_BASE} ]; then
    FILES=`ls -r ${FILE}`
    FILES=(${FILES})
    BRANCH=${FILES[0]}
    BRANCH=${BRANCH//.txt/}
    BRANCH=${BRANCH:$((${#BRANCH}-4))}    # 4 letters in tail
    BRANCH=$((${BRANCH}+1))
fi

BRANCH="000${BRANCH}"
BRANCH=${BRANCH:$((${#BRANCH}-4))}    # 4 letters in tail

FILE=${DIR}/${TM}-${BRANCH}.txt
echo ${FILE}

# mv ${TARGET} ${FILE}

C)について

投稿を読んでいて一番気になったのは連番の管理のところです。

>末尾に各秒ごとに連番を振りたいと考えています。
ということなので、同一秒で複数のファイルが作成されるのは想定されていて
同一秒で既に作られたファイルの状況を見て新しい連番を決定しないとなりません。
そうするとタイミングによっては同じ連番を取り合ってしまうことが起きます。

今2つのファイルが同一秒で作成されたところを想像してみてください。

incronからそれらの2つのファイルに対してそれぞれコマンドが起動されます。
それぞれのコマンドはファイルの作成状況を調べて自分の連番を決めようとします。
まだ同一秒のファイルがなければ、2つのコマンドそれぞれが0を自分の連番とするし
1つでもファイルがあればその連番+1を自分の連番としますから
いずれにしろそれぞれコマンドが認識する連番の値は同じになります。
結果としてリネームするファイル名が同じなるのでいずれかのコマンドが失敗します。

従ってリネームの失敗を検出したコマンドは、自分の処理の一部として
連番を決めるところからリトライする必要が出てきます。

リトライすれば成功したコマンドがリネームしたファイルが出来ているので
その連番+1を使ってリネームが成功するはずです。

B)で書いてみたコードにはこのリトライ処理はいれていません。

以上です

オフライン

 

Board footer

Powered by FluxBB