我有一个包含许多文件的目录。
例子:
aaa.txt
bbb.txt
ccc.txt
ddd.txt
给定一个任意字符串(不一定是其中一个文件的名称),我想找到在该字符串之前排序的所有文件(按正常字母顺序排序)。
示例:给出ccc.txt
我想要查找bbb.txt
和aaa.txt
。
文件名仅包含普通 ASCII 字符。LC_ALL=C
可以假设。没有隐藏文件(以 开头.
)。
一个潜在的解决方案可能是这样的(带有一个弥补的测试):
$ find -isnamelessthan ccc.txt
aaa.txt
bbb.txt
如何才能做到这一点?
答案1
和zsh
:
print -rC1 -- **/*(NDe['[[ $REPLY:t < ccc.txt ]]'])
在哪里:
print -rC1 --
print
s 其参数r
aw 和 on1
C
olumn**/
匹配任何级别的子目录(包括 0)以进行递归搜索,就像find
这样。(...)
是进一步限定匹配的全局限定符:N
print
: nullglob 以便在没有匹配项时不报告错误(并且不打印任何内容):D
: dotglob 与 with 一样find
,不排除隐藏文件e['code']
:运行代码以查看是否应选择该文件。这里的代码是对(正在考虑的文件路径)的 ail (基本名称)与[[ $REPLY:t < ccc.txt ]]
进行词法比较(使用memcmp()
,而不是区域设置感知strcoll()
函数)。t
$REPLY
ccc.txt
在 GNU 系统上,您可以使用以下命令(在任何 shell 中)执行类似的操作:
find . -mindepth 1 -printf '%P\0' | sort -z |
LC_ALL=C gawk -v RS='\0' -F/ '$NF < "ccc.txt"'
在哪里:
-mindepth 1
,排除起始文件 (.
)。您也可以使用标准! -name .
,尽管它不能扩展到其他起始文件²。-printf '%P\0'
打印文件相对于起始文件的路径,例如dir/aaa.txt
for./dir/a.txt
,由 NUL 字节分隔(文件路径中不能出现的唯一字节值)。sort -z
按照/globsstrcoll()
的方式对列表进行排序。ls
LC_ALL=C
将(如sstrcoll()
使用的)转换为(在基于 ASCII 的系统上)awk
<
memcmp()
-v RS='\0'
将输入R
ecordS
分隔符设置为 NUL 字节(ORS
保留换行符的默认值)-F/
,缩写,将字段分离器-v FS=/
设置为。F
S
/
$NF < "ccc.txt"
:将最后一个字段与"ccc.txt"
词法进行比较,如果为真,则运行默认操作({print}
的缩写{print $0}
)来打印记录。
对于-isnamelessthan
find
谓词,您可以执行以下操作(在 zsh 中):
alias -g -- -isnamelessthan='-exec zsh -c "[[ \$1:t < \$2 ]]" zsh {}'
用作:
find . -isnamelessthan ccc.txt ';' -print
(效率不高,因为它运行一个实例来zsh
检查每个文件)。
¹ 虽然 glob 本身是根据语言环境进行排序的,所以使用strcoll()
² 虽然您不能find /path/to/dir ! -name dir
这样做,因为这会排除内部也调用的文件dir
,但您可以这样做find /path/to/dir/. ! -name .
。
答案2
假设您的文件名不包含换行符,则使用任何 awk:
$ printf '%s\n' * | awk '$0 >= "ccc.txt"{exit} 1'
aaa.txt
bbb.txt
答案3
如果test
您系统上的实用程序支持非标准<
运算符来确定一个字符串是否排在另一个字符串之前,那么您可以将其与以下命令一起使用find
:
find . -exec test {} '<' ./ccc.txt \; -print
或者,
find . -exec [ {} '<' ./ccc.txt ] \; -print
在这里,我使用文件相对于当前目录的路径名进行比较,因为其他路径名也是如此。请注意,该<
运算符需要用'<'
,"<"
或引起来\<
,以防止 shell 将其解释为重定向运算符。
如果测试成功,-print
谓词将导致输出路径名。
添加进一步的测试,例如将搜索限制为仅常规文件,避免下降到子目录,并避免隐藏名称(或您可能提出的任何其他条件):
find . ! -path . -prune ! -name '.*' -type f -exec [ {} '<' ./ccc.txt ] \; -print
答案4
gawk
Stéphane Chazelas 和 Ed Morton 使用和发布了很好的答案awk
,似乎用一行就优雅地解决了问题。
然而,未来的程序员必须知道awk
如何详细理解这些解决方案。因此,我认为在我的情况下最好使用简单的 for 循环。
我会接受 Stéphane 的答案,因为它有最好的解释,但也在这里留下我自己的解决方案。
target="ccc.txt"
arr=()
target="ccc.txt"
arr=()
for f in * ; do
# You can compare all string (also non-numeric) using <
# -f handles the case when there are no files present
if [[ -f $f && $f < $target ]] ; then arr+=("$f") ; fi
done
echo ${arr[@]}