按文件类型拆分大目录树

按文件类型拆分大目录树

我的 Ubuntu 10.10 台式机上有一个大型数据目录(20-30Gb),其中包含许多原始数据文件、处理后的数据文件以及从处理后的数据生成的各种脚本、表格、图形等。数据目录已经积累了很多年,而且结构很差——“有一天”我会把它整理出来,但总有更重要的事情要做。

我现在正在切换到在线备份服务,为了减少备份所需的时间和所需的在线存储,我想拆分原始数据,这占用了大量空间,但很容易替换为已在其他地方存档,同时保留其在目录结构中的一般位置。换句话说,我想从这样的地方开始:

/data/A/raw1.data
/data/A/raw2.data
/data/A/raw3.data
/data/A/processed.txt
/data/A/figure.eps
/data/A/plot.gnu
/data/B/raw4.data
/data/B/processed.txt
... etc.

/data/A/processed.txt
/data/A/figure.eps
/data/A/plot.gnu
/data/B/processed.txt
... etc.

/raw_data/A/raw1.data
/raw_data/A/raw2.data
/raw_data/A/raw3.data
/raw_data/B/raw4.data
... etc.

因此,原始数据文件从 /data 交换到 /raw_data,但保留其在目录结构中的位置,而处理后的数据和关联文件保留在同一位置。整体文件结构比这复杂和无序得多,但可取之处是所有原始数据都可以通过文件类型(主要是.fits和.sdf)来识别。

我确信,通过正确的命令组合和/或几行 bash 脚本,这是微不足道的,但我的命令行知识仅限于基础知识,我宁愿问也不愿冒险搞砸:)

而且,顺便说一句,有没有一种简单的方法来查找原始数据中的重复项 - 将具有相同的文件名+大小,不一定是时间戳,当从存档下载数据时,时间戳会被重置,尽管要完全确定我需要通过 dfits 管道每个重复的候选者并 grep 适合标头中的时间戳。

答案1

一种方法是使用rsync一些特制的包含/排除规则以及在同步后删除源文件的选项,如下所示:

rsync -av --include "*/" --include='*.fits' --include='*.sdf' \
    --exclude='*' --remove-source-files /data/ /raw_data/

如果您想在循环中逐步移动,以便可能包含其他操作,则需要一个执行如下操作的脚本:

DIR1="/data"
DIR2="/raw_data"

find "$DIR1" -type f \( -iname '*.fits' -or -iname '*.sdf' \) -print0 |
    while read -d $'\0' file; do
        mkdir -p "$DIR2/$(basename "$file")"
        mv "$file" "$DIR2/$(basename "$file")"
    done

答案2

有很多文件复制工具允许使用足够灵活的规则构建目标目录名称(zcprsyncpax、...)。不幸的是,它们中很少有允许按需移动(而不是复制)和创建目标目录。因此,我将展示一些分两遍完成的方法:首先创建所有可能必需的目标目录,然后执行移动。

Perl重命名

rename如果您编写了必要的 Perl 部分,Debian 和 Ubuntu 附带的Perl程序可以在需要时创建目标目录。

shopt -s globstar       # make **/ traverse directories recursively (requires bash 4)
rename 'BEGIN {use File::Path}
        s!^/data!/raw_data!;
        m!(.*)/!; mkpath($1)' /data/**/*.raw

在 zsh 中,省略该shopt -s globstar行;**默认表示递归遍历。在 bash 和 zsh 之外的 shell 中,您需要使用find递归遍历(请参见下面的示例)。如果您只有单级目录,则不必担心这一切。

创建目标目录

在 zsh 中(解释:/ 全局限定符表示仅匹配目录,并且eglob 限定符将后面给出的转换应用于每个名称):

mkdir /data/**/*(/e\''REPLY=${REPLY/data/raw_data}'\')

与其他外壳:

find /data -type d \
     -exec sh -c 'for d; do mkdir "/raw_data${d#/data}"; done' _ {} +

如果你只有一层子目录,那就简单多了:

for d in /data/*/; do mkdir "/raw_data${d#/data}"; done

移动文件 (zsh)

autoload zmv
zmv -Q '/data/(**/)(*.raw)(.)' '/raw_data/$1$2'

移动文件(便携式)

find /data -name '*.raw' \
     -exec sh -c 'for x; do mv "$x" "/raw_data${x#/data}"; done' _ {} +

相关内容