根据时间戳获取最新的文件

11

我是shell脚本的新手,所以需要一些帮助来解决这个问题。

我有一个目录,其中包含以下格式的文件。这些文件位于名为/incoming/external/data的目录中。

AA_20100806.dat
AA_20100807.dat
AA_20100808.dat
AA_20100809.dat
AA_20100810.dat
AA_20100811.dat
AA_20100812.dat

如您所见,该文件的文件名包括一个时间戳。即[RANGE] _ [YYYYMMDD] .dat。

我需要做的是使用文件名上的时间戳而不是系统时间戳找出最新日期的文件,并将文件名存储在变量中并将其移动到另一个目录中,其他文件则移至另一个目录。


[RANGE] 可以是任意两个字符的组合吗?这会有很大的区别,正如你可能已经从已有的答案中注意到的那样。 - Marc Reside
是的,它们可以不同。而且相同的文件夹中还会包含其他类型的文件,其名称不像上面所示的那样。 - ziggy
7个回答

25

对于那些只想要一个答案的人,这里它是:

ls | sort -n -t _ -k 2 | tail -1

这是我思考的过程。
我会假设 [RANGE] 部分可以是任何内容。
从我们知道的开始。
  • 工作目录:/incoming/external/data
  • 文件格式:[RANGE]_[YYYYMMDD].dat
我们需要在目录中找到最近的 [YYYYMMDD] 文件,并将文件名存储下来。
可用的工具(我只列出了与此问题相关的工具...通过实践识别它们变得更容易): 我想我们不需要 sed,因为我们可以使用 ls 命令的整个输出。使用 ls、awk、sort 和 tail,我们可以像这样获取正确的文件(请记住,您需要根据您的操作系统检查语法):
NEWESTFILE=`ls | awk -F_ '{print $1 $2}' | sort -n -k 2,2 | tail -1`

然后只需要将下划线放回去,这应该不太难。

编辑:我有点时间,所以至少在Solaris中修复了该命令的使用。

以下是复杂的第一步(假设目录中的所有文件都具有相同的格式:[RANGE]_[yyyymmdd].dat)。我敢打赌有更好的方法来完成这个任务,但这可以处理我的测试数据(事实上,我刚刚发现了更好的方法;请参见下文):

ls | awk -F_ '{print $1 " " $2}' | sort -n -k 2 | tail -1 | sed 's/ /_/'

在写这个的时候,我发现你可以这样做:

ls | sort -n -t _ -k 2 | tail -1

我会将其分解为几个部分。
ls

很简单...获取目录列表,只有文件名。现在我可以将其导入到下一个命令中。

awk -F_ '{print $1 " " $2}'

这是AWK命令。它允许您以特定方式修改输入行。在这里,我所做的只是指定awk应在下划线(_)处断开输入。我使用-F选项来实现这一点。这给了我每个文件名的两个部分。然后我告诉awk输出第一部分($1),后跟一个空格(“ ”),然后是第二部分($2)。请注意,空格是我最初建议中缺少的部分。此外,这是不必要的,因为您可以在下面的排序命令中指定分隔符。
现在输出被拆分成每行的[RANGE] [yyyymmdd].dat。现在我们可以对其进行排序:
sort -n -k 2

这个命令会根据第二个字段对输入内容进行排序,sort命令默认使用空格作为分隔符。在编写此更新时,我找到了sort的文档,允许您指定分隔符,因此AWK和SED是不必要的。将ls命令的输出通过以下sort命令进行管道传输:

sort -n -t _ -k 2

这样做可以达到相同的结果。现在你只需要最后一个文件,所以:
tail -1

如果你使用 awk 命令来分离文件的话(这只会增加额外的复杂度,所以不要那么做),你可以使用 sed 命令再次将空格替换为下划线:
sed 's/ /_/'

这里有一些好的信息,但我相信大多数人不会像这样读到底部。


我尝试过这个,但它没有起作用。您能解释一下它具体在做什么吗?谢谢! - ziggy
好的,我在测试后进行了更新。我不得不修复awk命令中的一些问题,然后发现它实际上并不需要。解决方案在顶部,解释很长,但并不必要,但我很享受写作。 - Marc Reside
这对我有效。请生我的孩子。 - James T Snell
我在SO上看到的最好的答案之一。谢谢。 - Kosta Kontos

4

这应该可以工作:

newest=$(ls | sort -t _ -k 2,2 | tail -n 1)
others=($(ls | sort -t _ -k 2,2 | head -n -1))

mv "$newest" newdir
mv "${others[@]}" otherdir

如果文件名中有空格,那么这段代码将无法正常工作,尽管您可以修改 IFS 变量来解决这个问题。


嗨,圆括号是用来做什么的? - ziggy
@ziggy:你的意思是第二行的外部集合吗?它们创建了一个数组,在最后一行中使用。 - Dennis Williamson
嗨,Dennis,我指的是内部和外部圆括号。我尝试运行上面的代码,但括号导致语法错误。我正在使用 Bourne shell。这些是 Korn shell 特定的结构吗? - ziggy
@ziggy:内部括号(实际上是 $())用于命令替换。它们比反引号更好,但执行相同的功能。我展示的语法是针对 Bash 的,你在问题中标记了它。它也应该适用于 ksh。$() 应该在 sh 中工作,但数组语法不会,因为 Bourne shell 没有数组。 - Dennis Williamson
@Dennis:谢谢。对我来说太多的壳了。现在我深入挖掘,我倾向于使用bash作为我的默认选择,这确实是我所思考的。虽然我不需要每天编写太多复杂的脚本,但我所有的脚本都是用sh编写的,偶尔会用ksh。 - Marc Reside
显示剩余2条评论

2

尝试:

$ ls -lr

希望这有所帮助。

嗨,使用系统时间戳对文件进行排序不就可以了吗?我对实际文件名的时间戳很感兴趣。 谢谢。 - ziggy
不,它根据您的语言环境按名称对文件进行排序。如果您想按系统时间戳排序,则需要使用“-t”标志。 - igor

1

使用:

ls -r -1 AA_*.dat | head -n 1

(假设没有其他与AA_*.dat匹配的文件)


1
ls -1 AA* |sort -r|tail -1

1
由于文件名称约定,字母顺序与日期顺序相同。我很确定在中,'*'会按字母顺序展开(但无法在手册页面中找到任何证据),ls肯定会这样做,因此具有最新日期的文件将是按字母顺序排列的最后一个文件。
因此,在中
mv $(ls | tail -1) first-directory
mv * second-directory

应该可以解决问题。

如果您想更具体地选择文件,请将*替换为其他内容 - 例如AA_*.dat


这也可以,但我试图避免依赖系统来为我排序(即通过ls命令)。谢谢。 - ziggy
为什么你不想依赖于 ls - 你所说的“系统”是什么意思? - Beano

1

我的解决方案与其他人类似,但更简单一些。

ls -tr | tail -1

它实际上依赖于ls对输出进行排序,然后使用tail获取最后一个列出的文件名。

如果所需的文件名具有前导点(例如.profile),则此解决方案将无法工作。

如果文件名包含空格,则此解决方案确实有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接