根据内容模式匹配将文件移动到目录

根据内容模式匹配将文件移动到目录

我想通过编写一个调用的脚本将包含特定内容的现有目录中的文件移动到files现有或新目录和子目录,将它们移动到 dir/subdir。fruit~/bin

例如,我在现有目录中有许多常规文件files,名称为 file1 file2 file3 .... file100。

ls

file1
file2 
file3 
file4 
... 
file100

文件的内容是:

cat file1

apple 1
789098

cat file2

orange 2
389342

cat file3

pear 1
678034

cat file4

grapes 3
123432

cat file5

apple 3
342534

cat file6

apple 3
234298

我想将具有相同第一行 field1 内容的文件移动到与 field1 同名的新目录,同时保持文件名不变。

  • file1, file5,file6前往apple
  • file2orange
  • file3pear
  • 等等

ls

apple pear grapes orange etc ...

./apple:
file1 file5 file6

./pear:
file3

/orange:
file2

然后我想创建一个新的子目录并将具有相同第一行 field2 内容的文件移动到该子目录。

  • 在目录下applefile1将转到子目录1file5将转到子目录3file6将转到子目录3
  • 在目录下orangefile2将转到子目录2
  • 在目录下pearfile3将转到子目录1
  • 等等

排序和移动后,文件应排序如下:

ls

apple pear grapes orange etc ...

./apple:
1 3

./apple/1:
file1

./apple/3:
file5 file6

./orange:
2

./orange/2:
file2

./pear
1

./pear/1
file3

如何使用 vi 编辑器循环遍历所有文件,将它们移动到 shell 中相应的目录和子目录?

答案1

$ find . -name 'file*' 
./file6
./file1
./file5
./file2
./file3
./file4

$ perl -lane '
    close(ARGV);
    mkdir $F[0] unless -e $F[0];
    mkdir "$F[0]/$F[1]" unless -e "$F[0]/$F[1]";
    rename $ARGV, "$F[0]/$F[1]/$ARGV" if (-d "$F[0]/$F[1]");
  ' file*

$ find . -name 'file*' 
./pear/1/file3
./grapes/3/file4
./orange/2/file2
./apple/1/file1
./apple/3/file5
./apple/3/file6

file{1..6}是您的示例文件。 perl 脚本依次打开每个文件,读取第一行并将其拆分到数组中@F(通过 perl 的-a命令行选项)。然后它关闭文件(这具有重置行计数器的副作用,$.),创建目录(如果它们尚不存在),并将文件移动到目录(如果它实际上是一个目录)(如果它已经存在,它有可能是一个常规文件或符号链接或其他东西而不是目录)。

没有至少一行的文件将被忽略。与预期格式不同的文件(即第一行包含两个字段,由任何类型的空格分隔,具有基本目录名称和子目录名称)将导致未定义的(可能是奇怪的,可能是灾难性的)结果。

这两个find命令用于显示运行 perl one-liner 之前和之后文件的位置。这是一个最低限度的脚本,不会产生任何输出。它也没有进行足够的错误检查或数据验证。


替代版本,作为独立脚本。编写它的唯一真正原因是解决
Stéphane 关于 perl-T选项的评论(在大多数情况下,这不会成为问题......但人们确实会用文件名做出病态疯狂甚至恶意的事情,因此谨慎/偏执并没有错) :

$ cat sort-move.pl 
#!/usr/bin/perl

use strict;
use File::Path qw(make_path);

while(<<>>) {
  my($dir,$subdir) = split;
  close(ARGV);
  make_path("$dir/$subdir");
  rename $ARGV, "$dir/$subdir/$ARGV" if (-d "$dir/$subdir");
}

运行它,例如,./sort-move.pl file*.除了目录创建错误现在是一种致命情况之外,结果将与单行版本完全相同。

它不进行额外的错误检查或数据验证 - 实际上,它做得更少(它依赖于make_path()核心 perlFile::Path模块中的函数来创建目录 -make_path()工作方式非常类似于mkdir -p)。换句话说,坏数据仍然会让它做坏事,所以不要给它提供坏数据。

但是,如果由于已存在且不是目录(或由于任何其他原因导致错误,例如文件系统空间或索引节点不足)而make_path失败,则该脚本现在将立即退出并显示错误消息。"$dir/$subdir"例如,如果我mkdir apple; touch apple/1在运行此脚本之前运行,则错误消息将为mkdir apple/1: File exists at ./sort-move.pl line 9.,该脚本不会创建任何目录,并且不会移动任何文件。我知道这一点是因为我正是这样做来测试它的。

完整的脚本可以优雅地处理错误情况。完整的脚本还有一个-nor--dry-run选项,仅显示它的内容不实际做就做。这不是一个完整的脚本,而是一个最小的做你想做的事情的一种方法的工作示例。

答案2

使用zshshell(vi根据您的要求,它具有像大多数 shell 一样的行编辑模式,尽管我不明白这有什么相关性):

typeset -A files=()

for file (file*(N.L+3))
  read -r dir subdir ignore < $file &&
    [[    $dir != (|.|..|*/*) ]] &&
    [[ $subdir != (|.|..|*/*) ]] &&
    files[$dir/$subdir]+=$file$'\0'

if (($#files))
  mkdir -p -- ${(k)files} &&
    for dir (${(k)files}) mv -i -- ${(0)files[$dir]} $dir/

files上面是一个A关联数组,其键是目标目录,由每个匹配文件第一行的前两个 IFS 分隔字段构造而成file*(N.L+3)(其名称以 开头file,是常规的 ( .) 且L长度大于 3 (x y\n大小4 是一行包含两个字段的最小文件))。

作为保护措施,我们禁止.,..或空目录组件或包含/.

关联数组元素的值是给定目标目录的文件列表,以 NUL 分隔。

然后,我们立即创建所有这些目录,只有成功后才开始在其中移动文件。

答案3

我很想这样做(在 Bash 或任何 POSIX-y shell 中):

for f in ./*; do
    read -r a b < "$f"
    mkdir -p -- "$a/$b"
    mv -- "$f" "$a/$b"
done

也就是说,循环遍历文件(read一行),将其拆分为两个字段,根据这些字段创建一个目录,然后将该文件移动到该目录。根据需要-p创建mkdir父目录并忽略现有目录的选项。

是的,这会做额外的工作,因为它为每个文件调用一次mkdirmv一次,是的,我假设您的文件不包含像/dev/foo.但它应该可以工作,并且不需要花太长时间来编写。

在示例文件上运行它会给出:

$ ls -R
.:
apple/  grapes/  orange/  pear/

./apple:
1/  3/

./apple/1:
file1

./apple/3:
file5  file6

./grapes:
3/
[...]

相关内容