查找排序在给定文件之前的文件

查找排序在给定文件之前的文件

我有一个包含许多文件的目录。

例子:

aaa.txt
bbb.txt
ccc.txt
ddd.txt

给定一个任意字符串(不一定是其中一个文件的名称),我想找到在该字符串之前排序的所有文件(按正常字母顺序排序)。

示例:给出ccc.txt我想要查找bbb.txtaaa.txt

文件名仅包含普通 ASCII 字符。LC_ALL=C可以假设。没有隐藏文件(以 开头.)。

一个潜在的解决方案可能是这样的(带有一个弥补的测试):

$ find -isnamelessthan ccc.txt
aaa.txt
bbb.txt

如何才能做到这一点?

答案1

zsh

print -rC1 -- **/*(NDe['[[ $REPLY:t < ccc.txt ]]'])

在哪里:

  • print -rC1 -- prints 其参数raw 和 on 1 Column
  • **/匹配任何级别的子目录(包括 0)以进行递归搜索,就像find这样。
  • (...)是进一步限定匹配的全局限定符:
    • Nprint: nullglob 以便在没有匹配项时不报告错误(并且不打印任何内容):
    • D: dotglob 与 with 一样find,不排除隐藏文件
    • e['code']:运行代码以查看是否应选择该文件。这里的代码是对(正在考虑的文件路径)的 ail (基本名称)与[[ $REPLY:t < ccc.txt ]]进行词法比较(使用memcmp(),而不是区域设置感知strcoll()函数)。t$REPLYccc.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.txtfor ./dir/a.txt,由 NUL 字节分隔(文件路径中不能出现的唯一字节值)。
  • sort -z按照/globsstrcoll()的方式对列表进行排序。ls
  • LC_ALL=C将(如sstrcoll()使用的)转换为(在基于 ASCII 的系统上)awk<memcmp()
  • -v RS='\0'将输入RecordS分隔符设置为 NUL 字节(ORS保留换行符的默认值)
  • -F/,缩写,将字段分离器-v FS=/设置为。FS/
  • $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

gawkSté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[@]}

相关内容