sea*_*bry 6 zsh parameter string
给定一个包含以下内容的目录:
note 1.txt
, 昨天最后修改note 2.txt
,上次修改前天note 3.txt
,今天最后修改获取数组的最佳方法是什么note 3
note 1
note 2
?
为了定义“最佳”,我更关心稳健性(在 macOS 中的 ZSH 上下文中),而不是效率和可移植性。
预期的用例是一个包含数百或数千个纯文本文件的目录,但是——冒着混淆问题的风险——这是我有一个更普遍的问题的一个特定案例,在文件路径上执行字符串操作的最佳实践是什么由命令打印喜欢ls
,find
,和mdfind
。
我一直在使用一个调用此命令的宏来实现上述目的:
ls -t | sed -e 's/.[^.]*$//'
Run Code Online (Sandbox Code Playgroud)
它从来没有失败过,但是:
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)
但是按修改日期排序似乎需要调用stat
and 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
。
但是,如果您可以依赖perl
或python
在那里,您将能够让它们生成和排序文件列表,并以 NUL 分隔输出(尽管如果您想支持亚秒级精度,可能不那么容易移植)。
Run Code Online (Sandbox Code Playgroud)ls -t | sed -e 's/.[^.]*$//'
对于包含换行符的文件名将无法正常工作(IIRC 某些版本的 macOS 在/etc
默认情况下确实带有此类文件名)。对于包含未形成有效字符的字节序列的文件名,它也可能失败,.
或者[^.]
可能无法匹配它们。不过,它可能不适用于 macOS,可以通过将区域设置设置为C
/ POSIX
for来修复sed
。
该.
应进行转义(s/\.[^.]*$//
),因为它是正则表达式运算符的任何字符相匹配否则,原来点较少文件,比如foobar
为空字符串。
请注意,要打印字符串raw,它是:
print -r -- "$string"
Run Code Online (Sandbox Code Playgroud)
以print "$string"
$string
开头的值将失败-
,甚至引入命令注入漏洞(例如尝试使用string='-va[$(uname>&2)1]'
,此处使用无害uname
命令)。并且会破坏包含\
字符的值。
您的:
Run Code Online (Sandbox Code Playgroud)find . -type f -print0 | while IFS= read -d '' -r l ; do print "${${l%.*}##*/}" ; done
还有一个问题是您.*
在删除目录组件之前删除了 。因此,例如 a./foo.d/bar
将变为foo
而不是bar
并且./foo
将变为空字符串。
关于find
在各种 shell 中处理输出的安全方法,请参阅为什么循环查找的输出是不好的做法?