ファイル、ディレクトリの末尾に日付を付けるスクリプト

シェルスクリプト作成練習第二弾

gdgd書いてたら結構時間掛かりました。
お題は「**ファイル、ディレクトリの末尾に日付を付けるスクリプト**」
日付はYYMMDD形式です。
今回はパイプで出力した内容をスクリプトに渡せる仕様にしてみました。
for文で何の工夫もなくぶん回しているので出力が多いと死ぬほど時間掛かると思います。
変数名が分かりづらいのはご愛嬌ということで。
名前の付け方よくわからないんですよね。
あとやっぱり汚い。
マークダウンの色合いのお陰でなんとか助かってます。
シェルスクリプトはプログラミングではないので汚くなってしまうのは仕方ないと思うのですが。
とりあえずスクリプト本体をば・・・。

# 本体

```bash
#!/bin/bash
# 引数のファイルの末尾に日付を付ける

cmdname=datenumburing
ver=1.0

# 使用法
usage() {
    cat <<EOF
使用法: ${cmdname} [OPTION]... [FILE or DIRECITORYNAME]...
指定したファイル、ディレクトリの末尾に日付を付けるスクリプトです。

Options:
    -a             ファイル、ディレクトリの末尾に "-after" を付ける
    -b             ファイル、ディレクトリの末尾に "-before" を付ける
    -h, --help     使い方を表示して終了する
    -v, --version  バージョン情報の表示
EOF
    exit 0
}

# バージョン情報
version() {
    echo -e "version. ${ver}\n2015/05/20 ine1127"
            exit 0
}

# "help参照" を意味するメッセージ
try_help() {
    echo "Try '${cmdname} --help' for more information."
            exit 1
}

# 引数不足時のエラー
no_opt_param() {
    echo "${cmdname}: オプションには引数が必要です -- '$(echo \ ${param} | sed 's/^ -*//' )'"
            try_help
}

# 複数オプション指定時のエラー
multi_opt() {
    if [ ${j} -ge 2 ]; then
        echo "${cmdname}: オプションは複数指定できません"
        try_help
    fi
}

# ファイル、ディレクトリが見つからなかった時のエラー
no_param() {
    echo "\`${param}' を stat できません: そのようなファイルやディレクトリはありません"
    try_help
}

# 引数がなかった時のエラー
error() {
    echo "${cmdname}: オペランドがありません"
    try_help
}

# ファイルかディレクトリが存在しているか確認
# 存在しない場合は各パラメータに1を入れ、どちらも1を返した場合
# no_paramを実行
judge() {
    if [ ${#array_J[@]} -ne 0 ]; then
        for param in "${array_J[@]}"
        do
            dir=""
            file=""
            [ ! -d ${param} ] && dir=1
            [ ! -f ${param} ] && file=1 || file=0
            if [ "${dir}" = "${file}" ]; then
                no_param
            fi

            array_A+=(${param})

        done

    fi
}

# パイプも引数もなければエラーを返す
[ ! -p /dev/stdin ] && rep_i=1
[ -z ${1} ] && rep_j=1 || rep_j=0

[ "${rep_i}" = "${rep_j}" ] && error

# 標準出力がパイプで渡されているか確認
# パイプで渡されていたら、配列に格納
# ついでにファイルとディレクトリが存在しているか確認(judge関数)
if [ -p /dev/stdin ]; then
    array_P=$(cat /dev/stdin)
    array_J=(${array_P[@]})
    judge
fi

# オプション解析 & エラー判定
i=0
j=0

for opt in "$@"
do
    case "$opt" in

        # -h or --help or -v or --version は他に引数があっても問答無用で出力
        # -v  -h が被った場合、先に宣言されている方を出力
        -h|--help)
            usage
        ;;

        -v|--version)
            version
        ;;

        # -a オプションの指定がされていた場合array_Pに文字列が入っているか
        # 引数が1つ以上指定されていれば変数 y に "-after" を代入
        # 2つ以上オプションが指定されていればエラーを返す
        # 何も指定されていない場合もエラーを返す
        -a)
            if [ ! -z "$2" -o ! -z ${#array_P[@]} ]; then
                ((++j))
                y="-after"
                multi_opt
                shift
            else
                no_opt_param
            fi
        ;;

        # -a オプションと同様
        -b)
            if [ ! -z "$2" -o ! -z ${#array[@]} ]; then
                ((++j))
                y="-before"
                multi_opt
                shift
            else
                no_opt_param
            fi
        ;;

        -|--)
            error
        ;;

        --*)
            echo "${cmdname}: オプション '${param}' を認識できません"
            try_help
        ;;

        -*)
            echo "${cmdname}: 無効なオプション -- '$(echo ${param} | sed 's/^-*//' | sed 's/-*$//' )'"
            try_help
        ;;

        # オプション & エラー解析に引っかからなかった引数は全て配列に格納
        *)
            #((++i))
            #if [ ${i} -ge 2 ]; then
            #    echo "${cmdname}: 引数は複数指定できません"
            #    try_help
            #fi
            array_O+=("${opt}")
        ;;

    esac

done

# 引数が配列に格納されているか確認
# 引数のファイルとディレクトリが存在しているか確認
if [ $# -ne 0 ]; then
    array_J=(${array_O[@]})
    judge
fi

# 変数 array_A になにも入っていなければ
# パイプで渡されると、標準出力がされない場合やただの文字列でも
# コマンドが通ってしまい、結果として何も出力されない為、それを防ぐ為の判定文
[ "${array_A}" = "" ] && error

# 日付を設定
today=.`date +%y%m%d`

# 標準出力させる
# ファイル名.日付(-before or -after)となっていた場合、日付の後ろに-2を付ける
for ans in "${array_A[@]}"
do
    # 末尾に "/" がついていた場合、除外(ディレクトリ等)
    ans=`echo ${ans} | sed -e 's/\/$//'`

    # ファイル名
    filename=${ans}${today}${y}

    # lsとgrepとtailでファイル名を抽出
    ls | grep -w ${filename} | tail -n 1 2>&1 /dev/null

    # 検索に引っかからなかった場合、ファイル名をそのまま出力
    if [ $? -eq 1 ]; then
        echo ${filename}
    else
        # それ以外の場合Num変数に+1してファイル名を書き換えて出力
        Num=`ls | grep -w ^${filename} | wc -l`
        [ ${Num} -eq 0 ] && Num="" || Num=-`expr ${Num} + 1`
        filename=${ans}${today}${Num}${y}
        echo ${filename}
    fi

done

exit 0
```

# スクリプト動作
## 通常の動作
スクリプト単体では動作しません。
引数が必要です。

```bash
$ ./datenumbering.sh
datenumburing: オペランドがありません
Try 'datenumburing --help' for more information.
```

引数には、ファイル名かディレクトリ名を入れます。
カレントディレクトリにファイルかディレクトリが無いと、エラーになります。

```bash
$ ./datenumbering.sh file
`file' を stat できません: そのようなファイルやディレクトリはありません
Try 'datenumburing --help' for more information.
```

適当にファイルを作って検証してみます。

```bash
$ touch test
$ ls
datenumbering.sh  test
```

スクリプトを動かしてみます。

```bash
$ ./datenumbering.sh test
test.150525
```

末尾に日付が付いて出力されます。
因みに出力するだけなので、別に名前を変えているわけではありません。

ディレクトリでも同じような動きになります。

```bash
$ mkdir testdir
$ ls
datenumbering.sh  test  testdir

$ ./datenumbering.sh testdir/
testdir.150525
```

末尾の "/" スラッシュはディレクトリの場合Tabキーで補完すると付いてしまいますが、sedでどうにか消しています。

パス有りでも記述できます。

```bash
$ ./datenumbering.sh /etc/sysconfig/network-scripts/ifcfg-eth0
/etc/sysconfig/network-scripts/ifcfg-eth0.150525
```

## パイプ

とりあえずパイプで出力を渡せるようにしています。

```bash
$ echo test | ./datenumbering.sh
test.150525
```

ファイル、ディレクトリが存在していて標準出力されれば全部拾ってくれるので、lsの結果を渡すこともできます。

```bash
$ ls -a | ./datenumbering.sh
..150525
...150525
datenumbering.sh.150525
test.150525
testdir.150525
```


## ファイル名変更

名前を変更したりコピーを作成する場合は以下のようにします。

```bash
$ cp -ip test `echo test | ./datenumbering.sh`
$ ls
datenumbering.sh  test  test.150525
```

再度同じコマンドを実行すると、「ファイル名.日付-2」といった形になります。

```bash
$ cp -ip test `echo test | ./datenumbering.sh`
$ ls
datenumbering.sh  test  test.150525  test.150525-2
```

もう一度実行してみましょう。

```bash
$ cp -ip test `echo test | ./datenumbering.sh`
$ ls
datenumbering.sh  test  test.150525  test.150525-2  test.150525-3
```

同じコマンドを実行する毎に番号が加算されていく仕組みです。

## オプション

オプションに "-a" と "-b" を用意しています。
"-a" は、ファイル、ディレクトリ名の末尾に "-after" と付け、
"-b" は、ファイル、ディレクトリ名の末尾に "-before" と付けます。

```bash
$ ./datenumbering.sh -a test
test.150525-after
$ ./datenumbering.sh -b test
test.150525-before
```


できる事はこれだけです。
手動で取得したバックアップファイルを日付入りで記述出来ねーかなと思って作ってみたのですが、実用的じゃないですね。
スクリプトの一部に組み込んでどうこうできるかも知れませんが・・・。
・・・とりあえず今は書くことに意味があるかなあとも思います。