Bash函数查找最新的文件匹配模式


141

在Bash中,我想创建一个函数,该函数返回与特定模式匹配的最新文件的文件名。例如,我有一个文件目录,例如:

Directory/
   a1.1_5_1
   a1.2_1_4
   b2.1_0
   b2.2_3_4
   b2.3_2_0

我想要以“ b2”开头的最新文件。我该怎么做呢?我需要在~/.bash_profile脚本中包含它。


4
有关更多答案的提示,请参见superuser.com/questions/294161/…。排序是获取最新文件的关键步骤
Wolfgang Fahl 2016年

Answers:


229

ls命令具有-t按时间排序的参数。然后,您可以使用抓取第一个(最新的)head -1

ls -t b2* | head -1

但要注意:为什么不应该解析ls的输出

我个人的看法:ls仅当文件名包含空格或换行符之类的有趣字符时,解析才是危险的。如果可以保证文件名不会包含有趣的字符,则解析ls是非常安全的。

如果您正在开发一个脚本,该脚本打算在许多不同情况下在许多系统上由许多人运行,那么我非常建议您不要解析ls

这是“正确”的操作方法:如何在目录中找到最新(最新,最早,最旧)文件?

unset -v latest
for file in "$dir"/*; do
  [[ $file -nt $latest ]] && latest=$file
done

8
其他说明:如果要对目录执行此操作,则应将-d选项添加到ls中,例如'ls -td <pattern> |。头-1'
ken.ganong

5
分析LS链接说没有做到这一点,并建议在方法BashFAQ 99。我正在寻找1-liner而不是脚本中要包含防弹的东西,因此我将像@lesmana一样继续不安全地解析ls。
同名的

1
@Eponymous:如果您正在寻找一个不使用易碎品的衬板lsprintf "%s\n" b2* | head -1将为您完成。
David Ongaro '16

2
@DavidOngaro这个问题并不表示文件名是版本号。这是关于修改时间。即使使用文件名假设也b2.10_5_2无法解决该问题。
同名的

1
您的班轮公司给了我正确的答案,但是“正确”的方法实际上是给了我最早的文件。知道为什么吗?
NewNameStat

15

的组合find,并ls可以很好地用于

  • 没有换行符的文件名
  • 文件数量不是很大
  • 文件名不是很长

解决方案:

find . -name "my-pattern" -print0 |
    xargs -r -0 ls -1 -t |
    head -1

让我们分解一下:

随着find我们可以匹配所有有趣的文件是这样的:

find . -name "my-pattern" ...

然后使用-print0我们可以将所有文件名安全地传递给ls这样的文件:

find . -name "my-pattern" -print0 | xargs -r -0 ls -1 -t

find可以在此处添加其他搜索参数和模式

find . -name "my-pattern" ... -print0 | xargs -r -0 ls -1 -t

ls -t将按修改时间对文件进行排序(从最新到最新),并一行打印一个。您可以-c用来按创建时间排序。注意:这将与包含换行符的文件名中断。

最终head -1获得排序列表中的第一个文件。

注意: xargs对参数列表的大小使用系统限制。如果超过此大小,xargs将调用ls多次。这将破坏排序,也可能破坏最终输出。跑

xargs  --show-limits

检查系统上的限制。

注2:使用find . -maxdepth 1 -name "my-pattern" -print0如果你不想通过子文件夹进行搜索文件。

注意3:如果@没有匹配文件,则@starfry- -r参数所指出的xargs阻止调用。谢谢您的建议。ls -1 -tfind


2
这比基于ls的解决方案要好,因为它适用于文件太多的目录,而ls令人窒息。
Marcin Zukowski

find . -name "my-pattern" ... -print0给我find: paths must precede expression: `...'
Jaakko,

哦! ...代表“更多参数”。如果您不需要它,请忽略它。
鲍里斯·布罗德斯基

2
我发现,如果没有与模式匹配的文件,它可以返回与模式不匹配的文件。发生这种情况是因为find没有将任何内容传递给xargs,然后xargs调用了没有文件列表的ls,从而使它可以在所有文件上工作。解决方案是添加-r到xargs命令行,该命令告诉xargs如果在标准输入中未收到任何内容,则不要运行其命令行。
starfry

@starfry谢谢!不错的收获。我添加-r了答案。
Boris Brodski '19

7

这是必需的Bash函数的可能实现:

# Print the newest file, if any, matching the given pattern
# Example usage:
#   newest_matching_file 'b2*'
# WARNING: Files whose names begin with a dot will not be checked
function newest_matching_file
{
    # Use ${1-} instead of $1 in case 'nounset' is set
    local -r glob_pattern=${1-}

    if (( $# != 1 )) ; then
        echo 'usage: newest_matching_file GLOB_PATTERN' >&2
        return 1
    fi

    # To avoid printing garbage if no files match the pattern, set
    # 'nullglob' if necessary
    local -i need_to_unset_nullglob=0
    if [[ ":$BASHOPTS:" != *:nullglob:* ]] ; then
        shopt -s nullglob
        need_to_unset_nullglob=1
    fi

    newest_file=
    for file in $glob_pattern ; do
        [[ -z $newest_file || $file -nt $newest_file ]] \
            && newest_file=$file
    done

    # To avoid unexpected behaviour elsewhere, unset nullglob if it was
    # set by this function
    (( need_to_unset_nullglob )) && shopt -u nullglob

    # Use printf instead of echo in case the file name begins with '-'
    [[ -n $newest_file ]] && printf '%s\n' "$newest_file"

    return 0
}

它仅使用Bash内置函数,并且应处理名称包含换行符或其他不寻常字符的文件。


1
您可以使用nullglob_shopt=$(shopt -p nullglob),然后在以后$nullglob放回nullglob以前的样子。
gniourf_gniourf 2014年

@gniourf_gniourf建议使用$(shopt -p nullglob)是一个很好的建议。我通常尝试避免使用命令替换($()或反引号),因为它很慢,尤其是在Cygwin下,即使命令仅使用内置函数也是如此。同样,命令在其中运行的子外壳上下文有时可能导致它们以意外的方式运行。我还尝试避免将命令存储在变量中(例如nullglob_shopt),因为如果错误地获取变量的值,可能会发生非常糟糕的事情。
pjh 2014年

我感谢对细节的关注,这些细节在被忽略时会导致模糊的故障。谢谢!
罗恩·伯克

我喜欢您采用了一种更独特的方式来解决问题!可以肯定的是,在Unix / Linux中,有多种方法可以“剥皮cat!”。即使需要花费更多的工作,它也可以显示人的概念。+1!
Pryftan

3

异常的文件名(例如包含有效\n字符的文件可能会对这种解析造成破坏。这是在Perl中执行此操作的一种方法:

perl -le '@sorted = map {$_->[0]} 
                    sort {$a->[1] <=> $b->[1]} 
                    map {[$_, -M $_]} 
                    @ARGV;
          print $sorted[0]
' b2*

那是在那里使用的施瓦茨变换


1
愿施华兹与您同在!
内森·蒙特莱昂

这个答案可能有用,但考虑到文档不佳,我不相信它。
Wolfgang Fahl,2013年

1

您可以使用stat文件glob和decorate-sort-undecorate,并将文件时间添加在前面:

$ stat -f "%m%t%N" b2* | sort -rn | head -1 | cut -f2-

不。“状态:无法读取'%m%t%N'的文件系统信息:没有这样的文件或目录”
Ken Ingram

我想这可能适用于Mac / FreeBSD版本的stat,如果我没有记错的话。要在其他平台上获得类似的输出,可以使用stat -c $'%Y\t%n' b2* | sort -rn | head -n1 | cut -f2-
Jeffrey Cash

1

暗魔术函数咒语,适合那些想要上述find ... xargs ... head ...解决方案的人,但是它们以易于使用的函数形式出现,因此您不必思考:

#define the function
find_newest_file_matching_pattern_under_directory(){
    echo $(find $1 -name $2 -print0 | xargs -0 ls -1 -t | head -1)
}

#setup:
#mkdir /tmp/files_to_move
#cd /tmp/files_to_move
#touch file1.txt
#touch file2.txt

#invoke the function:
newest_file=$( find_newest_file_matching_pattern_under_directory /tmp/files_to_move/ bc* )
echo $newest_file

印刷品:

file2.txt

这是:

与给定格式匹配的给定目录下具有最旧文件修改时间戳的文件名。


1

使用find命令。

假设您使用的是Bash 4.2+,请使用-printf '%T+ %p\n'文件时间戳值。

find $DIR -type f -printf '%T+ %p\n' | sort -r | head -n 1 | cut -d' ' -f2

例:

find ~/Downloads -type f -printf '%T+ %p\n' | sort -r | head -n 1 | cut -d' ' -f2

有关更有用的脚本,请参见此处的最新脚本:https : //github.com/l3x/helpers


与包含空格的文件名一起使用change cut -d''-f2,3,4,5,6,7,8,9 ...
valodzka

0

有一种更有效的方法可以实现这一目标。考虑以下命令:

find . -cmin 1 -name "b2*"

该命令将查找一分钟前产生的最新文件,并在“ b2 *”上进行通配符搜索。如果您需要最近两天的文件,那么最好使用以下命令:

find . -mtime 2 -name "b2*"

“。” 代表当前目录。希望这可以帮助。


9
这实际上并没有找到“最新文件匹配模式”……它只是找到了一分钟前创建或两天前修改的所有文件匹配模式。
GnP

这个答案是基于提出的问题。另外,您可以调整命令以查看一天左右前的最新文件。这取决于您要执行的操作。
纳法尔

“苦苦挣扎”不是答案。就像将其发布为答案一样:“只需调整find命令,然后根据您要执行的操作找到答案”。
Kennet Celeste

不确定不必要的评论。如果您觉得我的答案没有根据,请提供正确的理由说明我的答案对“示例”没有意义。如果无法这样做,请不要再发表评论。
纳法尔

1
您的解决方案要求您知道何时创建了最新文件。那不是问题所在,所以不,您的答案不是基于所提出的问题。
Bloke Down The Pub
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.