如何`tail`目录中的最新文件

Ita*_*vka 23 linux shell shell-script

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

Poi*_*nty 28

tail `ls -t | head -1`
Run Code Online (Sandbox Code Playgroud)

如果您担心文件名带有空格,

tail "`ls -t | head -1`"
Run Code Online (Sandbox Code Playgroud)

  • 如果您牺牲稳健和正确,则很容易变得干净和简单。 (6认同)
  • 嗯,这取决于你在做什么,真的。对于所有可能的文件名,始终适用于任何地方的解决方案非常好,但在受限情况下(例如,日志文件,具有已知的非奇怪名称)可能是不必要的。 (2认同)

pho*_*ogg 26

千万不能解析LS的输出!解析 ls 的输出既困难又不可靠

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

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)"
Run Code Online (Sandbox Code Playgroud)

现在不完全是一个单行者,是吗?在这里,它再次作为一个 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
}
Run Code Online (Sandbox Code Playgroud)

而现在作为一个oneliner:

tail -- "$(latest-file-in-directory)"
Run Code Online (Sandbox Code Playgroud)

如果所有其他方法都失败了,您可以将上述函数包含在您的文件中,.bashrc并考虑解决问题,但有一个警告。如果您只是想完成工作,则无需进一步阅读。

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

多汁的细节

出于好奇,这是对其工作原理、为什么安全以及为什么其他方法可能不安全的乏味解释。

危险,威尔罗宾逊

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

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

find . -print0
Run Code Online (Sandbox Code Playgroud)

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

find . -printf '%T@.%p\0'
Run Code Online (Sandbox Code Playgroud)

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

我现在有

find . -maxdepth 1 \! -type d -printf '%T@.%p\0'
Run Code Online (Sandbox Code Playgroud)

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

以正确的顺序获取

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

| sort -znr -t. -k1,2
Run Code Online (Sandbox Code Playgroud)

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

1000000000.0000000000../some-file-name
Run Code Online (Sandbox Code Playgroud)

这意味着sort将着眼于先1000000000,然后0000000000订购此记录时。该-n选项告诉sort在比较这些值时使用数字比较,因为这两个值都是数字。这可能并不重要,因为数字是固定长度的,但它没有害处。

给予的另一个开关sort-r“反向”。默认情况下,数字排序的输出将首先是最低数字,对其进行-r更改,使其最后列出最低数字,然后首先列出最高数字。由于这些数字是时间戳,更高意味着更新,这会将最新记录放在列表的开头。

只是重要的部分

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

| while IFS= read -r -d '' record
Run Code Online (Sandbox Code Playgroud)

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

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

printf '%s' "$record" | cut -d. -f3-
Run Code Online (Sandbox Code Playgroud)

在这里,整个记录通过printf管道传递到cut;在 bash 中,您可以使用here 字符串进一步简化,以cut -d. -3f- <<<"$record"获得更好的性能。我们告诉cut两件事:首先-d,它应该有一个特定的分隔符来识别字段(与sort使用分隔符一样.)。第二个cut被指示-f只打印来自特定字段的值;字段列表作为一个范围给出,该范围3-指示来自第三个字段和所有后续字段的值。这意味着cut将读取并忽略所有内容,直到并包括.它在记录中找到的第二个,然后将打印剩余部分,即文件路径部分。

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

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

  • 在文件名中使用特殊字符的人应该得到他们得到的一切:-) (6认同)
  • 看到paxdiablo说出这种话已经够痛苦了,但后来有两个人投票了!故意编写错误软件的人应该得到他们所得到的一切。 (6认同)
  • 因此,由于 find 中缺少 -printf 选项,上述解决方案不适用于 osx,但由于 stat 命令的差异,以下解决方案仅适用于 osx... . -type f -exec stat -f "%m {}" {} \;| sort -n | tail -n 1 | cut -d ' ' -f 2)` (4认同)
  • “不幸的是,即使是 GNU `head` 和 `tail` 也不接受使它们对空分隔输入进行操作的开关。” 我对 `head` 的替换:`... | grep -zm &lt;数字&gt; ""`。 (2认同)

小智 6

您可以使用:

tail $(ls -1t | head -1)
Run Code Online (Sandbox Code Playgroud)

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

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

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


Ann*_*sum 6

zsh

tail *(.om[1])
Run Code Online (Sandbox Code Playgroud)

参见:http: //zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Qualifiers,这里m表示修改时间m[Mwhms][-|+]n,前面o表示按一种方式排序(按另一种方式O排序)。这.意味着只有常规文件。在括号内[1]选择第一项。挑三用[1,3],取最老用[-1]

它很短而且不使用ls.