如何在目录中尾部最新文件


20

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


1
加油,程序员需要追尾!
阿米特2010年

关闭仅适用于转移到超级用户或服务器故障。该问题将在那里存在,并且可能会有更多感兴趣的人找到它。
Mnementh'3

真正的问题是在目录中找到最新的更新文件,并且我相信已经回答了(无论是在这里还是在超级用户上,我都无法回忆起)。
dmckee,2010年

Answers:


24

千万不能解析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)"

现在还不是一个班轮,是吗?在这里,它还是一个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用作定界符,甚至在将单个文件路径从一个程序传递到另一个程序时,以不会阻塞任意字节的方式进行操作,这一点很重要。有许多看似正确的方法可以解决此问题和其他问题,但这些方法都失败了,甚至(假设是偶然地)假设文件名中将没有换行或空格。两种假设都不安全。

对于今天的目的,第一步是找到一个以空分隔的文件列表。如果您有诸如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,则可以要求它使用空分隔记录,而不是使用-zswitch。对于标准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。该-n选项告诉sort您在比较这些值时使用数字比较,因为两个值都是数字。这可能并不重要,因为数字是固定长度的,但没有害处。

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

只是重要的一点

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

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

首先,我将其设置IFS为不对文件列表进行分词。接下来,我讲read两件事:不要解释输入(-r)中的转义序列,并且输入以空字节(-d)分隔;此处的空字符串''用于表示“无定界符”,又以null分隔。每个记录都将读入变量,record以便每次while循环迭代时,它都有一个时间戳和一个文件名。注意这-d是一个GNU扩展;如果您只有一个标准,则read此技术将无效,并且您的资源也很少。

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

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

在这里,整个记录都通过printf管道传递到那里cut;在bash中,您可以使用here字符串进一步简化此操作,cut -d. -3f- <<<"$record"以提高性能。我们讲cut两件事:首先-d,它应该有一个用于标识字段的特定定界符(就像使用sort定界符一样.)。其次cut是指示-f仅打印特定字段中的值;字段列表以范围的3-形式给出,该范围指示来自第三字段和所有后续字段的值。这意味着cut它将读取并忽略直到.在记录中找到的所有内容,包括第二个内容,然后将打印其余部分,即文件路径部分。

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

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


1
@AakashM:因为您可能会得到“令人惊讶”的结果,例如,如果文件名中包含“不寻常”字符(几乎所有字符都是合法的)。
John Zwinck 2010年

6
在文件名中使用特殊字符的人们应得到的所有东西都是:-)

6
看到paxdiablo的话很痛苦,但随后两个人投了赞成票!编写越野车软件的人应该得到他们应得的一切。
John Zwinck 2010年

4
因此,由于find中缺少-printf选项,因此上述解决方案在osx上不起作用,但是由于stat命令中的差异,以下解决方案仅在osx上有效...也许它仍然对tail -f $(find . -type f -exec stat -f "%m {}" {} \;| sort -n | tail -n 1 | cut -d ' ' -f 2)
某些

2
“不幸的是,即使是GNU headtail也不接受使它们对以空分隔的输入进行操作的开关。” 我的替代品head… | grep -zm <number> ""
卡米尔Maciorowski

22
tail `ls -t | head -1`

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

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

1
但是,当您的最新文件包含空格或特殊字符时,会发生什么?使用$()代替``并引用您的子外壳程序以避免出现此问题。
phogg 2010年

我喜欢这个。干净简单。应该的。

6
如果您牺牲了健壮性和正确性,那么很容易做到干净简单。
phogg 2010年

2
好吧,这实际上取决于您在做什么。对于所有可能的文件名,始终可以在任何地方使用的解决方案非常好,但是在受约束的情况下(例如,日志文件使用已知的非怪异的名称),则不必要。

到目前为止,这是最干净的解决方案。谢谢!
demisx

4

您可以使用:

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

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

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

请记住,如果这是最新创建的目录条目,则冒着获取目录的风险。我在别名中使用了该技巧,以在仅包含那些日志文件的目录中编辑最新日志文件(来自旋转集)。


-1是没有必要的,ls这是否给你当它是在管道。比较lsls|cat,例如。
暂停,直到另行通知。

在Linux下可能就是这种情况。在“真正”的Unix中,进程不会根据输出的去向改变行为。这会使管道调试真的很烦人:-)

嗯,不确定那是正确的-ISTR在通过过滤器管道输出时,必须发出“ ls -C”才能在4.2BSD下获得列格式的输出,而且我敢肯定Solaris下的ls以相同的方式工作。什么是“一个真正的Unix”?

行情!行情!文件名中有空格!
诺曼·拉姆西

@TMN:一种真正的Unix方法是非人类消费者不要依赖ls。“如果输出到终端,则格式是实现定义的。” -这是规格。如果要确保必须说ls -1或ls -C。
phogg 2010年

4

在POSIX系统上,无法获取“最后创建的”目录条目。每个目录条目atimemtime并且ctime,但是违背了微软的Windows,在ctime没有确实意味着创建时间,但“上次状态更改时间”。

因此,最好的办法是“尾巴最近修改的文件”,其他答案对此进行了说明。我将使用以下命令:

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

注意ls命令周围的引号。这使得该片段几乎可以使用所有文件名。


辛苦了 开门见山。+1
诺曼·拉姆西

4

我只想看看可以使用的文件大小更改。

watch -d ls -l


1

大概有一百万种方法可以做到这一点,但是我要做的是:

tail `ls -t | head -n 1`

反引号之间的位(引号,如字符)被解释,结果返回到尾部。

ls -t #gets the list of files in time order
head -n 1 # returns the first line only

2
反引号是邪恶的。使用$()代替。
威廉·珀塞尔

1

一个简单的:

tail -f /path/to/directory/*

对我来说很好。

问题是获取启动tail命令后生成的文件。但是,如果您不需要这样做(因为上面的所有解决方案都不在乎),则星号只是更简单的解决方案,IMO。



0

有人张贴了它,然后由于某种原因将其删除,但这是唯一可行的方法,所以...

tail -f `ls -tr | tail`

您必须排除目录,不是吗?
艾米特

1
我最初发布了此内容,但删除了它,因为我同意Sorpigal的观点,解析输出ls不是最明智的事情……
ChristopheD 2010年

我需要它又快又脏,没有目录。因此,如果您要添加答案,我将接受一个答案
Itay Moav -Malimovka,2010年

0
tail -f `ls -lt | grep -v ^d | head -2 | tail -1 | tr -s " " | cut -f 8 -d " "`

说明:

  • ls -lt:按修改时间排序的所有文件和目录的列表
  • grep -v ^ d:排除目录
  • 头-2开始:解析所需的文件名

1
+1代表聪明,-2代表解析ls输出,-1代表不引用子外壳,-1代表魔术“ field 8”假设(这是不可移植的!),最后-1代表过于聪明。总体得分:-4。
phogg 2010年

@Sorpigal同意。很高兴成为坏榜样。
阿米特(Amit 8'10)2010年

有没有想象那将是错误的就这么多罪名
阿米特

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.