使用不存在的目标目录重命名命令

使用不存在的目标目录重命名命令

我正在尝试使用如下命令将文件自动分组到子目录中:

$ rename 's/(.)(.)(.+)/$1\/$2\/$1$2$3/' *.*

使用参数进行的试运行-n显示了我想要的内容:

test.jpg renamed as t/e/test.jpg

但实际重命名失败了

Can't rename test.jpg t/e/test.jpg: No such file or directory

因为子目录还不存在。

如何在不事先手动创建所有子目录的情况下实现此目的?

答案1

重命名和移动到某个地方是有区别的。在这种情况下,最简单的方法(在现代中bash)是循环遍历所有文件:

for f in *.*
do
    d=${f::1}/${f:1:1}
    [ -d "$d" ] || mkdir -p "$d"
    mv "$f" "$d"
done

解释

这利用了 bash 鲜为人知的“参数扩展/子字符串扩展”功能。

${var:offset:length}

    扩展到最多length的值的字符var 从指定的字符开始offset

。   [稍稍转述自重击(1).]

我没有看到哪里记录了这一点,但是,offset是从零开始的,0第一个字符也是 从零开始,1第二个字符也是从零开始。如果 offset为 null(缺失),则视为零。

答案2

作为延伸科斯塔斯的回答,

for f in *.*
do
    if [ "${f:0:1}" = . ]  ||  [ "${f:1:1}" = . ]
    then
        printf 'Cannot handle "%s"\n' "$f"
    else
        d=${f:0:1}/${f:1:1}
        if ! { [ -d "$d" ] || mkdir -p "$d"  &&  mv "$f" "$d"; }
        then
            break
        fi
    fi
done
  • 我添加了(第一个) if 来处理像M.Butterfly.jpgand 这样的文件名Q.jpg,因为你不能(真的)创建像M/.and 这样的目录Q/.。同样,.Dragon.mp4 将尝试创建一个名为 的目录 ./D1

  • 我添加了&&(在 mkdir和 之间mv),因此,如果 mkdir失败,我们不会尝试将文件移动到不存在的目录中。

  • 我添加了第二个,if因为如果出现问题,脚本终止并让用户找出(并修复)问题是有意义的。
    (例如,如果您不小心在没有写入权限的目录中运行脚本,或者文件系统已满,因此无法创建新的子目录,该怎么办?)

    我可以把它写成

    [ -d "$d" ] || mkdir -p "$d"  &&  mv "$f" "$d  ||  break
    

    但这些&&/||序列很快就会变得混乱。

另一种变化:

for f in *.*
do
    if [ "${f:0:1}" = . ]  ||  [ "${f:1:1}" = . ]
    then
        printf 'Cannot handle "%s"\n' "$f"
    else
        d=${f:0:1}/${f:1:1}
        if ! { [ -d "$d" ] || mkdir -p "$d"; }          # Note: no "mv" command in the loop.
        then
            break
        fi
    fi
done
rename 's/(.)(.)(.+)/$1\/$2\/$1$2$3/' *.*

这样做的优点是它只调用rename一次而不是调用mv一千次。它有几个缺点:

  • M.Butterfly.jpg像和 这样的文件Q.jpg被传递到rename.
  • rename即使 mkdir失败也会运行。 (这可能很容易解决。一种方法是将 替换 breakexit,但这看起来不优雅。)

我没有rename和你相同的版本,但我尝试过mv M.Butterfly.jpg M/.,它只是移到M.Butterfly.jpg了目录中 M。因此,如果您使用我的第二个解决方案,当您列出目录时 M,您将看到子目录、、、、a等(包含诸如、、、、等文件)和一些纯文件(如 )。然后你就可以随心所欲地处理它们。 ________________ 1 实际上,这些命令会成功,但它们会创建名为、  和 的 目录,这可能不是您想要的。eioMany.jpgMemorable.jpgMinnesota.jpgMoments.jpgM.Butterfly.jpg

MQD

答案3

这可以完全在脚本中完成rename,因为rename允许使用任何perl 代码,它不仅仅限于简单的s///运算符。

以下工作在简单的情况下,即所有要重命名的文件都在当前目录中,并rename直接在命令行上传递而不带./前缀(这通常不是一个好主意,因为可能-会出现以下开头的文件名解释为rename) 的命令行选项。

如果文件名包含目录前缀,则它将不起作用,例如它们被传递给renameviafind或 as ./*.*

$ touch test.jpg test.txt
$ rename -n 'my ($a, $b) = (/(.)(.)/);
             mkdir $a unless -e $a;
             mkdir "$a/$b" unless -e "$a/$b";
             if (-e "$a/$b" && ! -d "$a/$b") {
               print STDERR "$_: $a/$b exists but is not a directory!\n";
             } else {;
               s/(.)(.)(.+)/$1\/$2\/$1$2$3/;
             }' *.*
rename(test.jpg, t/e/test.jpg)
rename(test.txt, t/e/test.txt)

更好的版本可以使用文件::基本名称文件路径模块,这两个模块都包含在 perl 中。

$ rename -n 'use File::Basename;
             use File::Path qw(make_path);

             my ($bn, $dn) = fileparse($_);
             my ($a, $b) = ($bn =~ /(.)(.)/);
             make_path("$dn$a/$b", { verbose => 1 });
             $_ = "$dn$a/$b/$bn"' ./*.*
mkdir ./t
mkdir ./t/e
rename(./test.jpg, ./t/e/test.jpg)
rename(./test.txt, ./t/e/test.txt)

这将在每个文件的原始目录下创建两个子目录。该make_path()函数File::Path已经检查目标目录是否存在(如果存在但不是目录,则将其视为致命错误,但如果您希望它只打印警告并继续执行,则可以实现自己的错误处理下一个文件名)。

如果您愿意,可以通过设置$dn为目标顶级目录来对其进行硬编码,以将所有文件重命名为特定的目录树(您仍然需要使用该File::Basename模块从完整路径名中提取基本名称)。

相关内容