精彩细节

精彩细节

在 shell 中,如何才能获取tail目录中创建的最新文件?

答案1

tail `ls -t | head -1`

如果你担心文件名中有空格,

tail "`ls -t | head -1`"

答案2

不是解析 ls 的输出!解析 ls 的输出是困难且不可靠

如果您必须这样做,我建议您使用 find。最初,我在这里提供了一个简单的示例,只是为了向您提供解决方案的要点,但由于这个答案似乎有点流行,我决定修改它以提供一个可以安全复制/粘贴并与所有输入一起使用的版本。你坐得舒服吗?我们将从一行代码开始,它将为您提供当前目录中的最新文件:

tail -- "$(find . -maxdepth 1 -type f -printf '%T@.%p\0' | sort -znr -t. -k1,2 | while IFS= read -r -d '' -r record ; do printf '%s' "$record" | cut -d. -f3- ; break ; done)"

现在它还不是一个单行代码,对吧?下面再次将其作为 shell 函数并格式化以便于阅读:

latest-file-in-directory () {
    find "${@:-.}" -maxdepth 1 -type f -printf '%T@.%p\0' | \
            sort -znr -t. -k1,2 | \
            while IFS= read -r -d '' -r record ; do
                    printf '%s' "$record" | cut -d. -f3-
                    break
            done
}

现在一句话:

tail -- "$(latest-file-in-directory)"

如果其他方法都失败了,您可以将上述函数包含在您的代码中.bashrc,这样问题就解决了,但有一个警告。如果您只是想完成工作,则无需继续阅读。

需要注意的是,以一个或多个换行符结尾的文件名仍然无法正确传递tail。解决这个问题很复杂,我认为如果遇到这种恶意文件名,就会出现相对安全的行为,即“没有这样的文件”错误,而不是更危险的行为,这就足够了。

精彩细节

对于好奇的人来说,这是对其工作原理、为什么它安全以及为什么其他方法可能不安全的冗长解释。

危险,威尔·罗宾逊

首先,唯一可以安全地分隔文件路径的字节是 null,因为它是 Unix 系统上文件路径中唯一被普遍禁止使用的字节。处理任何文件路径列表时,重要的是只使用 null 作为分隔符,并且,在将单个文件路径从一个程序传递到另一个程序时,要以不会因任意字节而阻塞的方式进行。有许多看似正确的方法可以解决这个问题和其他问题,但这些方法都失败了,因为它们假设(甚至是无意的)文件名中不会有换行符或空格。这两种假设都不安全。

对于今天的目的,第一步是从 find 中获取以空字符分隔的文件列表。如果您有GNU 等find支持,这很容易:-print0

find . -print0

但这个列表仍然没有告诉我们哪一个是最新的,所以我们需要包含该信息。我选择使用 find 的-printf开关,它允许我指定输出中显示的数据。并非所有版本都find支持-printf(它不是标准的),但 GNU find 支持。如果你发现自己没有,-printf你将需要依赖,-exec stat {} \;此时你必须放弃所有可移植性的希望,因为stat这不是标准的。现在我将继续假设你有 GNU 工具。

find . -printf '%T@.%p\0'

这里我要求 printf 格式%T@为自 Unix 纪元开始以来的修改时间(以秒为单位),后跟一个句点,然后是一个表示秒的分数的数字。我在此基础上添加了另一个句点,然后是%p(这是文件的完整路径),最后以一个空字节结尾。

我现在有

find . -maxdepth 1 \! -type d -printf '%T@.%p\0'

这可能不言而喻,但为了完整起见-maxdepth 1,不会find列出子目录的内容并\! -type d跳过您可能不想要的目录tail。到目前为止,我在当前目录中有带有修改时间信息的文件,所以现在我需要按修改时间排序。

按正确顺序进行

默认情况下,sort它的输入是换行符分隔的记录。如果您有 GNU,sort您可以使用开关要求它改为使用空分隔的记录-z。;对于标准,sort没有解决方案。我只对按前两个数字(秒和秒的分数)排序感兴趣,而不想按实际文件名排序,所以我告诉了sort两件事:首先,它应该将句点(.)视为字段分隔符,其次,在考虑如何对记录进行排序时,它应该只使用第一个和第二个字段。

| sort -znr -t. -k1,2

首先,我将三个不带任何值的短选项捆绑在一起;-znr这只是一种简洁的说法-z -n -r)。之后-t .(空格是可选的)告诉sort字段分隔符并-k 1,2指定字段编号:第一个和第二个(sort从一开始计数字段,而不是从零开始)。请记住,当前目录的示例记录如下所示:

1000000000.0000000000../some-file-name

这意味着在排序此记录时sort将首先查看1000000000,然后查看。该选项指示在比较这些值时使用数字比较,因为两个值都是数字。这可能并不重要,因为数字的长度是固定的,但它没有坏处。0000000000-nsort

给出的另一个开关sort用于-r“反向”。默认情况下,数字排序的输出将首先显示最小数字,-r将其更改为最后列出最小数字,首先列出最大数字。由于这些数字是时间戳,因此数字越大表示越新,这会将最新记录放在列表的开头。

只关注重要部分

随着文件路径列表的出现,sort我们正在寻找的答案就在列表顶部。剩下的就是找到一种方法来丢弃其他记录并删除时间戳。不幸的是,即使是 GNUhead也不tail接受开关来使它们对空分隔输入进行操作。相反,我使用 while 循环作为一种穷人的head

| while IFS= read -r -d '' record

首先我取消设置IFS,这样文件列表就不会受到分词的影响。接下来我要说read两件事:不要解释输入中的转义序列(-r)并且输入用空字节(-d)分隔;这里空字符串''用于表示“无分隔符”,也就是用空字节分隔。每个记录都将读入变量record,这样每次while循环迭代时,它都有一个时间戳和一个文件名。请注意,这-d是一个 GNU 扩展;如果您只有一个标准,read这种技术将不起作用,而且您几乎没有办法。

我们知道record变量有三个部分,全部由句点字符分隔。使用该cut实用程序可以提取其中的一部分。

printf '%s' "$record" | cut -d. -f3-

在这里,整个记录被传递到那里,printf并从那里通过管道传输到cut;在 bash 中,你可以使用这里是字符串cut -d. -3f- <<<"$record"获得更好的性能。我们告诉cut两件事:首先-d,它应该使用特定的分隔符来标识字段(就像使用sort分隔符一样)。第二是指示仅打印特定字段的值;字段列表以范围的形式给出,表示第三个字段和所有后续字段的值。这意味着将读取并忽略它在记录中找到的第二个字段之前的所有内容,然后打印其余部分,即文件路径部分。.cut-f3-cut.

打印了最新的文件路径后,无需继续:break退出循环而不让其转到第二个文件路径。

剩下的唯一事情就是tail在该管道返回的文件路径上运行。您可能已经注意到,在我的示例中,我通过将管道括在子 shell 中来实现这一点;您可能没有注意到,我将子 shell 括在双引号中。这很重要,因为最后,即使付出了所有这些努力来确保任何文件名的安全,未加引号的子 shell 扩展仍然可能会破坏一切。更详细的解释可用,如果您有兴趣的话。调用的第二个重要但容易被忽视的方面tail是,我--在扩展文件名之前为其提供了选项。这将指示tail不再指定任何选项,并且后面的所有内容都是文件名,这使得处理以 开头的文件名变得安全-

答案3

您可以使用:

tail $(ls -1t | head -1)

$()构造启动一个子 shell,运行命令ls -1t(按时间顺序列出所有文件,每行一个)并通过管道head -1获取第一行(文件)。

然后将该命令的输出(最新文件)传递给tail进行处理。

请记住,如果这是最近创建的目录条目,则这样做会冒获取目录的风险。我曾在别名中使用过这个技巧,在仅包含这些日志文件的目录中编辑最新的日志文件(来自轮换集)。

答案4

在 POSIX 系统上,没有办法获取“最后创建的”目录条目。每个目录条目都有和atime,但与 Microsoft Windows 相反,mtimectimectime并不是指创建时间,而是指“上次状态改变的时间”。

因此,您能得到的最好的方法是“跟踪最近修改的文件”,这在其他答案中有所解释。我会选择这个命令:

尾部-f“$(ls-tr | sed 1q)”

请注意命令周围的引号ls。这使得代码片段适用于几乎所有文件名。

相关内容