使用 zsh 对不同目录中的文件列表进行排序

Sort list of files in different directories with zsh

假设我有以下 directory/file 结构

dirA/1fileAA.zsh
dirA/99fileAB.zsh
dirB/2fileBA.zsh
dirB/50fileBB.zsh
dirB/subdirA/20fileBAA.zsh

我想按文件名开头的数字排序,忽略任何目录,所以我得到

dirA/1fileAA.zsh
dirB/2fileBA.zsh
dirB/subdirA/20fileBAA.zsh
dirA/99fileAB.zsh
dirB/50fileBB.zsh

仅使用内置 zsh 功能。

实现此目标的最佳方法是什么?

我能想到重写字符串排序并写回吗? 或者更好地尝试创建关联数组并按键排序?

我还是zsh,想避免挖错方向,太多了。

这是一种仅使用 zsh 内置函数即可完成此操作的方法。该函数将文件名添加到每个路径的前面进行排序,然后将其删除:

function sortByFilename {
    local -a ary
    printf -v ary '%s/%s' ${${argv:t}:^argv}
    print -l ${${(n)ary}#*/}
}

使用您的示例目录设置,可以从 dirAdirB 的父目录调用它:

sortByFilename **/*.zsh

正在测试:

sortByFilename \
    dirA/1fileAA.zsh \
    dirA/99fileAB.zsh \
    dirB/2fileBA.zsh \
    dirB/50fileBB.zsh \
    '/leadslash/42 and spaces' \
    dirB/subdirA/20fileBAA.zsh

结果:

dirA/1fileAA.zsh
dirB/2fileBA.zsh
dirB/subdirA/20fileBAA.zsh
/leadslash/42 and spaces
dirB/50fileBB.zsh
dirA/99fileAB.zsh

件数:

  • printf -v ary <fmt> ...:使用格式字符串运行 printf,并将结果分配给 ary 数组。格式字符串的每次迭代都将成为数组中的另一个元素。
    • %s/%s:格式字符串。这将用斜杠分隔符连接两个字符串。
      如果输入中的值多于格式字符串中的说明符,printf 将重复格式模式。所以在这里,它将从输入数组中提取对(filename/pathname)。
    • ${${argv:t}:^argv}:这将生成一个包含文件名和完整路径的数组,即 (file1 path1 file2 path2 ...)
      • ${ :^ }:zsh 参数扩展,将压缩两个数组以创建交替文件名和路径。
      • ${argv:t}:文件名数组。使用 argv 中的函数位置参数和 :t 修饰符构建,returns 数组中每个元素的文件名组件。
      • argv: 完整路径数组。
  • print -l:在单独的行上打印输入的每个元素。
    • ${${(n)ary}#*/}:最终排序的路径列表。
      • ${(n)ary}:Returns数组按数字排序,使用n参数扩展标志。此时,ary 中的每个元素都是文件名、斜杠和输入路径的串联。
        由于文件名模式,n 标志在这里起作用;它将按十进制值排序,而不是在公共/空前缀内按词法排序,例如foo1 foo3 foo12.
      • ${ #*/}:从数组中每个元素的前面删除模式 */。这将删除用于排序的前缀,保留原始路径。
  • local -a ary:声明一个数组变量。这用作 printf -v 拆分其输出的指标。
    可以通过 (re-/mis-/ab) 使用预先声明的数组 argv 来消除这一行并使函数更短和更隐秘。
    function sortByFilename {
        printf -v argv %s/%s ${${argv:t}:^argv}
        print -l ${${(n)argv}#*/}
    }
    

编辑 - 单行版本:

(){print -l ${"${(n0)$(printf '%s/%s[=15=]' ${${argv:t}:^argv})}"#*/}} **/*.zsh

包括这一点只是因为创建单行线很有趣,而不是因为它被推荐。使用匿名函数、进程替换和附加参数扩展标志,这比上面的函数可读性更差,效率可能更低。