はじめに
日々の開発やサーバ運用では、「作業前にいったん固めておきたい」「今日触ったファイルだけ残したい」という場面がよくあります。
この記事では、csh で書いたバックアップスクリプト file_backup.csh を紹介します。
tar + gzip を使い、パーミッションを保ったままバックアップできます。
できること
| モード | オプション | 内容 |
|---|---|---|
| 全体バックアップ | (なし) | 指定ディレクトリ全体を yyyymmddhhmmss_bk.tar.gz に圧縮 |
| 変更ファイルバックアップ | -c |
本日更新されたファイルのみをファイル単位で ./bk に保存 |
- 圧縮形式:
tar.gz(パーミッション情報を含む) - 対象はディレクトリ指定
- ヘルプ:
-h/--help
環境
- csh / tcsh が使える Unix 系環境
-
tar/gzip/find/date/touchなど標準コマンド
chmod +x file_backup.csh
使い方
1. ディレクトリ全体をバックアップ
./file_backup.csh /path/to/target
実行ディレクトリに、次のようなアーカイブができます。
20261001153045_bk.tar.gz
2. 本日更新されたファイルだけバックアップ(-c)
./file_backup.csh -c /path/to/target
実行ディレクトリ配下の bk/ に、ファイル単位の tar.gz が出力されます。
bk/
target_subdir_foo.txt.tar.gz
target_another_bar.c.tar.gz
...
パスの / は _ に置換してファイル名にしているため、同名ファイルの衝突を避けやすくしています。
3. ヘルプ
./file_backup.csh -h
# または
./file_backup.csh --help
処理の流れ
共通処理
- 引数を解析(
-c/-h/ 対象ディレクトリ) - 対象がディレクトリか確認
- 絶対パス・親ディレクトリ名・ベース名を取得
変更ファイルモード(-c)
- 作業ディレクトリに
bk/を用意 -
/tmpに「本日 00:00」の参照ファイルをtouch -tで作成 -
find ... -newerで本日以降に更新された通常ファイルを列挙 - 各ファイルを個別に
tar czfで圧縮 - 成功/失敗件数を表示して終了
ポイントは、「今日 0 時より新しいファイル」 を参照ファイル比較で拾っていることです。
touch -t `date +%Y%m%d0000` "$reffile"
find "$base" -type f -newer "$reffile" -print > "$listfile"
全体バックアップモード
- 現在時刻から
YYYYMMDDhhmmssを生成 - 親ディレクトリへ移動し、対象ディレクトリ名を
tar czfで固める - 作業ディレクトリに
タイムスタンプ_bk.tar.gzを出力
set stamp = `date +%Y%m%d%H%M%S`
set archpath = "${workdir}/${stamp}_bk.tar.gz"
tar czf "$archpath" "$base"
実行例
全体バックアップ
$ ./file_backup.csh ./mydir
Full backup: /home/user/work/mydir -> /home/user/work/20261001154500_bk.tar.gz
Done: /home/user/work/20261001154500_bk.tar.gz
変更ファイルバックアップ
$ ./file_backup.csh -c ./mydir
Changed-file backup: 2 file(s) -> /home/user/work/bk
OK : mydir/a.txt -> mydir_a.txt.tar.gz
OK : mydir/src/b.c -> mydir_src_b.c.tar.gz
Done. success=2 fail=0 (output: /home/user/work/bk)
本日更新ファイルが無い場合:
No files modified today under: /home/user/work/mydir
設計上の工夫
-
相対パスで
tarする
親ディレクトリにcdしてから対象名だけを渡すため、アーカイブ内のパスが深くなりすぎません。 -
PID 付き一時ファイル
/tmp/bk_today_ref.$$のようにプロセス ID を付け、同時実行時の衝突を低減しています。 -
ファイル単位アーカイブ名の正規化
パス区切りを_に置換し、bk/配下での上書き衝突を防ぎます。 -
終了コード
失敗があればexit 1になるので、cron や他スクリプトから呼びやすいです。
注意点
- 「本日更新」は ローカルタイムの 0 時以降に更新されたファイル が対象です(タイムゾーンに注意)
-
-cの出力先は スクリプト実行時のカレントディレクトリ配下のbk/です - 全体バックアップの出力先も カレントディレクトリ です
- 巨大ディレクトリでは
findや個別tarに時間がかかることがあります - シンボリックリンクや特殊ファイルの扱いは
find -type f/tarの仕様に依存します
ソースコード
#!/bin/csh -f
#
# file_backup.csh - ディレクトリ/変更ファイルのバックアップ
#
# 使い方:
# file_backup.csh [-c] <バックアップ対象ディレクトリ>
#
# -c : 本日更新されたファイルのみをファイル単位で圧縮し、作業ディレクトリ/bk に保存
# (なし): 指定ディレクトリ全体を yyyymmddhhmmss_bk.tar.gz として圧縮
#
# 圧縮: tar + gzip(パーミッション情報を含む)
#
set mode = "full"
set target = ""
while ($#argv > 0)
switch ($argv[1])
case -c:
set mode = "changed"
shift
breaksw
case -h:
case --help:
echo "Usage: $0 [-c] <directory>"
echo " -c backup only files modified today (per-file tar.gz under ./bk)"
echo " (no option) full directory backup as yyyymmddhhmmss_bk.tar.gz"
exit 0
breaksw
default:
if ("$target" != "") then
echo "Error: too many arguments"
echo "Usage: $0 [-c] <directory>"
exit 1
endif
set target = "$argv[1]"
shift
breaksw
endsw
end
if ("$target" == "") then
echo "Usage: $0 [-c] <directory>"
exit 1
endif
if (! -d "$target") then
echo "Error: '$target' is not a directory"
exit 1
endif
set workdir = "`pwd`"
set bkdir = "${workdir}/bk"
set saved_pwd = "`pwd`"
cd "$target"
set abs_target = "`pwd`"
cd "$saved_pwd"
set parent = `dirname "$abs_target"`
set base = `basename "$abs_target"`
# 変更ファイルバックアップ (-c)
if ("$mode" == "changed") then
if (! -d "$bkdir") then
mkdir -p "$bkdir"
if ($status != 0) then
echo "Error: cannot create bk directory: $bkdir"
exit 1
endif
endif
set reffile = "/tmp/bk_today_ref.$$"
set listfile = "/tmp/bk_file_list.$$"
touch -t `date +%Y%m%d0000` "$reffile"
if ($status != 0) then
echo "Error: cannot create today reference file"
exit 1
endif
cd "$parent"
find "$base" -type f -newer "$reffile" -print > "$listfile"
set file_count = `wc -l < "$listfile" | tr -d ' '`
if ($file_count == 0) then
echo "No files modified today under: $abs_target"
cd "$workdir"
rm -f "$reffile" "$listfile"
exit 0
endif
echo "Changed-file backup: $file_count file(s) -> $bkdir"
set i = 1
set ok = 0
set fail = 0
while ($i <= $file_count)
set f = "`sed -n ${i}p $listfile`"
if ("$f" != "") then
set archname = `echo "$f" | sed 's/\//_/g'`
set archpath = "${bkdir}/${archname}.tar.gz"
tar czf "$archpath" "$f"
if ($status == 0) then
echo " OK : $f -> ${archname}.tar.gz"
@ ok++
else
echo " FAIL: $f"
@ fail++
endif
endif
@ i++
end
cd "$workdir"
rm -f "$reffile" "$listfile"
echo "Done. success=$ok fail=$fail (output: $bkdir)"
if ($fail != 0) then
exit 1
endif
exit 0
endif
# 全体バックアップ
set stamp = `date +%Y%m%d%H%M%S`
set archpath = "${workdir}/${stamp}_bk.tar.gz"
echo "Full backup: $abs_target -> $archpath"
cd "$parent"
tar czf "$archpath" "$base"
set tar_status = $status
cd "$workdir"
if ($tar_status == 0) then
echo "Done: $archpath"
exit 0
else
echo "Error: full backup failed"
exit 1
endif
まとめ
- オプションなしで ディレクトリ全体バックアップ
-
-cで 本日更新ファイルのみをファイル単位バックアップ -
tar.gzでパーミッション付き保存 - csh でも十分実用的なバックアップツールにできます
「作業前の保険」「今日の差分だけ残す」といった用途に、ぜひ使ってみてください。