列出目录中每个基本名称的最可靠方法,按修改日期排序?

sea*_*bry 6 zsh parameter string

给定一个包含以下内容的目录:

  • note 1.txt, 昨天最后修改
  • note 2.txt,上次修改前天
  • note 3.txt,今天最后修改

获取数组的最佳方法是什么note 3 note 1 note 2

为了定义“最佳”,我更关心稳健性(在 macOS 中的 ZSH 上下文中),而不是效率和可移植性。

预期的用例是一个包含数百或数千个纯文本文件的目录,但是——冒着混淆问题的风险——这是我有一个更普遍的问题的一个特定案例,在文件路径上执行字符串操作的最佳实践是什么由命令打印喜欢lsfind,和mdfind


我一直在使用一个调用此命令的宏来实现上述目的:

ls -t | sed -e 's/.[^.]*$//'
Run Code Online (Sandbox Code Playgroud)

它从来没有失败过,但是:

  • Greg 的 Wiki强烈建议不要解析ls. (解析ls实践,在“5. 永远不要做这些”下)。
  • sed在参数扩展会做的地方调用效率低下吗?

使用find(用 NUL 字符而不是换行符安全地分隔文件路径)和参数扩展来提取基本名称,这会产生一个未排序的列表:

find . -type f -print0 | while IFS= read -d '' -r l ; do print "${${l%.*}##*/}" ; done
Run Code Online (Sandbox Code Playgroud)

但是按修改日期排序似乎需要调用statand sort,因为 macOSfind缺少-printf标志,否则可能会很好地服务

最后,使用 ZSH 的glob 限定符

for f in *(om) ; do print "${f%.*}" ; done
Run Code Online (Sandbox Code Playgroud)

虽然不可移植,但最后一种方法对我来说似乎是最健壮和最有效的。这是正确的,find当我实际执行搜索而不是简单地列出目录中的文件时,是否有任何理由不应该使用上述命令的修改版本?

Sté*_*las 11

zsh

list=(*(Nom:r))
Run Code Online (Sandbox Code Playgroud)

绝对是最健壮的。

print -rC1 -- *(Nom:r)
Run Code Online (Sandbox Code Playgroud)

每行打印一个,或

print -rNC1 -- *(Nom:r)
Run Code Online (Sandbox Code Playgroud)

作为 NUL 分隔的记录,以便能够对该输出执行任何操作,因为 NUL 是文件路径中唯一不允许的字符。

*(N-om:r)如果您希望符号链接解析考虑修改时间(目标的 mtime 而不是像 with 那样的符号链接ls -Lt),请更改为。

:r(对于名称)是csh删除扩展名的历史修饰符(来自)。请注意,.bashrc如果您启用了该dotglob选项,它会变成空字符串,这只会在此处引起关注。

改为**/*(N-om:t:r)递归进行(:t尾部(basename),即去除目录组件)。

为任意文件名可靠地执行此操作ls将非常痛苦。

一种方法可能是运行ls -td -- ./*(假设文件名列表符合 arg 列表限制)并解析该输出,依赖于每个文件名以 开头的事实./,并生成一个 NUL 分隔的列表或一个 shell 引用的列表将它传递给外壳,但是除非您求助于perl或 ,否则可移植地执行此操作也非常痛苦python

但是,如果您可以依赖perlpython在那里,您将能够让它们生成和排序文件列表,并以 NUL 分隔输出(尽管如果您想支持亚秒级精度,可能不那么容易移植)。

ls -t | sed -e 's/.[^.]*$//'
Run Code Online (Sandbox Code Playgroud)

对于包含换行符的文件名将无法正常工作(IIRC 某些版本的 macOS 在/etc默认情况下确实带有此类文件名)。对于包含未形成有效字符的字节序列的文件名,它也可能失败,.或者[^.]可能无法匹配它们。不过,它可能不适用于 macOS,可以通过将区域设置设置为C/ POSIXfor来修复sed

.应进行转义(s/\.[^.]*$//),因为它是正则表达式运算符的任何字符相匹配否则,原来点较少文件,比如foobar为空字符串。

请注意,要打印字符串raw,它是:

print -r -- "$string"
Run Code Online (Sandbox Code Playgroud)

print "$string"$string开头的值将失败-,甚至引入命令注入漏洞(例如尝试使用string='-va[$(uname>&2)1]',此处使用无害uname命令)。并且会破坏包含\字符的值。

您的:

find . -type f -print0 | while IFS= read -d '' -r l ; do print "${${l%.*}##*/}" ; done
Run Code Online (Sandbox Code Playgroud)

还有一个问题是您.* 删除目录组件之前删除了 。因此,例如 a./foo.d/bar将变为foo而不是bar并且./foo将变为空字符串。

关于find在各种 shell 中处理输出的安全方法,请参阅为什么循环查找的输出是不好的做法?