根据文件名(带空格)递归排序和 mv 文件

根据文件名(带空格)递归排序和 mv 文件

我犯了一个错误,将文件一起转储到同一目录中。幸运的是,我可以根据文件名对它们进行排序: ''' 2019-02-19 20.18.58.ndpi_2_2688_2240.jpg ''' 其中#位或2在这种特定情况下,可以说是位置信息。范围是 0-9,文件名的长度都相同,因此数字始终位于文件名的相同位置(第 26 个字符,包括空格,两侧是下划线)。我发现了这个很棒的链接: 通过仅搜索文件名的一部分来查找文件的命令?

但是,我无法将输出通过管道传输到移动命令中。我尝试将输出循环到变量中,但这似乎也不起作用:

for f in find . -name '*_0_*' ; do  mv "$f" /destination/directory ; done

根据此链接,我可能将 * 或一些引号放在了错误的位置:mv: 无法统计 shell 脚本中没有这样的文件或目录

也就是说,我有很多目录,我想将它们分类到其他地方的相同目录结构中:

-Flowers (to be sorted)          -Flowers-buds               -Flowers-stems
     -Roses                          -Roses                      -Roses
        buds.jpg       ===>             buds.jpg     ===>           stems.jpg
        stems.jpg
        petals.jpg
     -Daisies                       -Daisies                    -Daisies
        buds.jpgs                       buds.jpg                   stems.jpg
        stems.jpg
        petals.jpg
     -Tulips                        -Tulips                     -Tulip
        buds.jpgs                       buds.jpg                  stems.jpg
        stems.jpg
        petals.jpg

...以及基于该数字的更多信息(#)。这是 bash 中实际做的事情吗?我在安装了 coreutils 的终端中运行 MacOS,因此这些工具的行为应该像 GNU linux,而不是 darwin (BSD)。

答案1

您可以使用 和 来完成此操作findmv但这不是最简单的方法。您使用的是 macOS,所以桀骜已预安装。 Zsh 附带了一个简洁的文件批量重命名工具,称为zmvzsh在终端中运行,然后在 zsh 中运行如下命令:

autoload zmv
zmv -n '[^_]#_([0-9])_*' '/elsewhere/${1}/${f}'

说明:

  • -n告诉zmv显示它将做什么,但实际上不做任何事情。当您对它显示的内容感到满意时,请再次运行该命令(不带-n.
  • 第一个非选项参数是通配符模式zmv将作用于匹配的文件(并忽略不匹配的文件)。
  • [^_]#表示除 之外的零个或多个字符_。 Zsh 使您可以访问与 bash 相同的通配符等。#是仅 zsh 的功能(在 bash 中以不同的语法提供),意思是“任意数量的前一个字符”。该模式[^_]#_[0-9]_*匹配任何在两个下划线之间包含单个数字且该数字之前没有其他下划线的文件名。
  • 周围的括号[0-9]使数字$1在替换文本中可用。
  • 替换文本可以使用${1}${2}等来引用源模式中带括号的片段,以及${f}引用完整的原始名称。

例如,上面的代码片段移动2019-02-19 20.18.58.ndpi_2_2688_2240.jpg/elsewhere/2/2019-02-19 20.18.58.ndpi_2_2688_2240.jpg.您的问题没有非常精确地描述需要移动的内容以及需要移动到的位置,但是您应该能够通过调整上面的命令来获得您想要的内容。如果您无法弄清楚,请编辑您的问题以添加必须匹配和不得匹配的具体示例。

如果子目录中有文件,则可以*/在模式的开头使用。如果需要匹配目录以引用它,则需要在 : 两边加上括号:不能在斜线两边加上括号。如果您想递归地遍历目录并匹配任何债务的文件,请使用for${NUM}***/递归通配符。作为例外,您需要使用(**/)来访问替换文本中的目录路径。在这种情况下,不使用括号通常会更容易,而使用${NUM}修饰语${f}原始路径的部分提取为整体。例如,执行与上面相同的重命名,但将文件从当前目录移动到 下的并行结构,但在文件名之前/elsewhere添加额外的级别0、等:1

autoload zmv
zmv -n '**/[^_]#_([0-9])_*' '/elsewhere/${f:h}/${1}/${f:t}'

如果很难根据名称来匹配文件,则另一种方法是根据更改时间来匹配它们。如果您只是将一堆文件复制到一段时间没有更改的目录中,则新文件就是具有最近更改时间的文件。您可以通过 zsh 中的更改时间来匹配文件全局限定符c。例如,要列出过去一小时内最后更改的文件:

ls -lctr *(ch-1)

您可以参考最近更改的带有 glob 限定符的文件oc(通过增加 ctime 进行排序)和(仅保留第一个[1,N]火柴)。例如,如果您知道刚刚将 42 个文件移动到该目录中,并且此后没有更改其中的任何内容:

ls -lctr *(oc[1,42])

如果您想将 glob 限定符与 一起使用zmv,则需要传递该-Q选项。例如,将文件移动到基​​于文件名称的目录(如上所示),但忽略过去一小时内未更改的所有文件:

zmv -n -Q '[^_]#_([0-9])_*(ch-1)' '/elsewhere/${1}/${f}'

zmv有一些安全措施来检查它不会覆盖文件,并且不存在冲突(两个源文件具有相同的目标名称)。如果您有大量文件,这些安全措施可能需要时间。如果zmv太慢并且重命名的结构保证不会发生任何冲突,则可以对循环中正在执行的特定重命名进行硬编码。即使你不使用 Zsh,它也往往会击败 bash,zmv因为它更好通配参数扩展机制。为了性能,您可以动态加载一个模块包含用于文件操作的内置函数

例如,如果常规文件的名称包含以下内容,则将常规文件从当前目录下移动到全新的层次结构_0_

zmodload -m -F zsh/files 'b:zf_*'
for x in **/*_0_*(.); do
  zf_mkdir -p /destination/directory/$x:h
  zf_mv ./$x /destination/directory/$x
done

:h是 zsh 的另一个功能:a历史修改器.)

要获取文件名中两个下划线之间的第一个数字,并将文件放入以该数字命名的目录中,您需要提取该数字。zmv对于带括号的组会自动执行此操作,这里我们需要手动执行此操作。

zmodload -m -F zsh/files 'b:zf_*'
for source in **/*_<->_*(.); do
  suffix=${${source:t}#*_<->_}
  n=${${source%$suffix}##*_}
  destination=${source:h}/$n/${source:t}
  zf_mkdir -p /destination/directory/$destination:h
  zf_mv ./$source /destination/directory/$destination
done

如果您想了解其他批量重命名文件的方法,可以浏览rename在此网站上标记的问题

¹文件的 inode 更改时间,简称为“更改时间”或“ctime”,是文件上次移动或上次更改其属性(例如权限)的时间。它与修改时间 (mtime) 不同。

答案2

shell 在空格上分割输入。您可以使用 bash globbing 和递归**来正确分割文件名:

shopt -s globstar
for f in **/*_0_*; do mv "$f" /dest/; done

如果它们都去同一个地方,则不需要循环:

shopt -s globstar
mv **/*_0_*  /dest/

...或 use find -exec,它将文件名直接从 find 传递到exec()调用,而不需要 shell 的参与:

find . -name '*_0_*' -exec mv {} /dest/ \;

或者使用read加号find -print0来分割空值。对于这个问题来说,它太过分了,在通配符且find -exec无法胜任任务时很有用:

while IFS=  read -r -d ''; do mv "$REPLY" /dest/; done < <( find . -name '*_0_*' -print0 )

要根据文件名更改顶级目标(如示例所示),请使用${var#remove_from_start}和截断文件名的各个部分${var%remove_from_end}。删除模式中的通配符将删除尽可能少的内容;要删除尽可能多的内容,请将操作字符加倍,如${…##…}or ${…%%…}

    shopt -s globstar
    for f in **/*_0_*; do            # Flowers/Roses/stems.jpg 
      file=${f##*/}                  # stems.jpg
      base=${file%.*}                # stems
      path=${f%/*}                   # Flowers/Roses
      toppath=${path%%/*}            # Flowers
      subpath=${path#*/}             # Roses
      dest=$toppath-$base/$subpath/  # Flowers-stems/Roses/
      [[ -d $dest ]] || mkdir -p "$dest"
      mv "$f" "$dest"
    done

相关内容