【问题标题】:Bash function to find newest file matching pattern用于查找最新文件匹配模式的 Bash 函数
【发布时间】:2011-08-18 15:51:11
【问题描述】:

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

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

我想要以“b2”开头的最新文件。我如何在 bash 中做到这一点?我需要在我的~/.bash_profile 脚本中有这个。

【问题讨论】:

标签: linux bash


【解决方案1】:

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

ls -t b2* | head -1

但请注意:Why you shouldn't parse the output of ls

我的个人意见:解析ls 仅在文件名可以包含有趣字符(如空格或换行符)时才危险。如果你能保证文件名不会包含有趣的字符,那么解析ls 是非常安全的。

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

这是“正确”的做法:How can I find the latest (newest, earliest, oldest) file in a directory?

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

【讨论】:

  • 其他人注意:如果你是为一个目录做这个,你会在 ls 中添加 -d 选项,就像这样 'ls -td |头 -1'
  • parsing LS 链接表示不要这样做,并推荐BashFAQ 99 中的方法。我正在寻找一个 1-liner 而不是包含在脚本中的防弹东西,所以我将继续像 @lesmana 一样不安全地解析 ls。
  • @Eponymous:如果您正在寻找不使用脆弱的ls 的单衬里,printf "%s\n" b2* | head -1 会为您解决。
  • @DavidOngaro 问题并没有说文件名是版本号。这是关于修改时间。即使文件名假设 b2.10_5_2 会杀死这个解决方案。
  • 你的一个班轮给了我正确的答案,但“正确”的方式实际上是给了我 oldest 文件。知道为什么吗?
【解决方案2】:

findls 的组合适用于

  • 不带换行符的文件名
  • 文件量不是很大
  • 文件名不是很长

解决办法:

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 所指出的 - 如果没有文件与 find 匹配,xargs-r 参数会阻止调用 ls -1 -t。谢谢你的建议。

【讨论】:

  • 这比基于 ls 的解决方案要好,因为它适用于包含大量文件的目录,其中 ls 会阻塞。
  • find . -name "my-pattern" ... -print0 给我find: paths must precede expression: `...'
  • 哦! ... 代表“更多参数”。如果您不需要它,请忽略它。
  • 我发现如果没有与模式匹配的文件,这会返回一个与模式不匹配的文件。发生这种情况是因为 find 没有向 xargs 传递任何内容,然后 xargs 调用没有文件列表的 ls ,导致它适用于所有文件。解决方案是将-r 添加到 xargs 命令行,告诉 xargs 如果在其标准输入上没有收到任何内容,则不要运行其命令行。
  • @starfry 谢谢!不错的收获。我在答案中添加了-r
【解决方案3】:

这是所需 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 内置函数,并且应该处理名称中包含换行符或其他不寻常字符的文件。

【讨论】:

  • 你能不能先用nullglob_shopt=$(shopt -p nullglob) 再用$nullglob 来恢复nullglob 以前的样子。
  • @gniourf_gniourf 建议使用 $(shopt -p nullglob) 是一个很好的建议。我通常会尽量避免使用命令替换($() 或反引号),因为它很慢,尤其是在 Cygwin 下,即使命令仅使用内置命令也是如此。此外,运行命令的子 shell 上下文有时会导致它们以意想不到的方式运行。我还尽量避免将命令存储在变量中(如nullglob_shopt),因为如果变量的值错误,可能会发生非常糟糕的事情。
  • 我很欣赏对细节的关注,这些细节在被忽视时会导致晦涩难懂的失败。谢谢!
  • 我喜欢您采用更独特的方式来解决问题!可以肯定的是,在 Unix/Linux 中,有不止一种方法可以“剥 cat!”。即使这需要更多的工作,它也有向人们展示概念的好处。有一个 +1!
【解决方案4】:

使用查找命令。

假设您使用的是 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

有关更有用的脚本,请参阅此处的 find-latest 脚本:https://github.com/l3x/helpers

【讨论】:

  • 处理包含空格的文件名 change cut -d' ' -f2,3,4,5,6,7,8,9 ...
  • Bash 的版本不重要。你需要有 GNU find,因为 -printf 选项是非标准的(所以通常,开箱即用,这仅适用于 Linux)。
【解决方案5】:

一个 Bash 函数,用于在与模式匹配的目录下查找最新文件

#1.  Make a bash function:
newest_file_matching_pattern(){ 
    find $1 -name "$2" -print0 | xargs -0 ls -1 -t | head -1  
} 
 
#2. Setup a scratch testing directory: 
mkdir /tmp/files_to_move;
cd /tmp/files_to_move;
touch file1.txt;
touch file2.txt; 
touch foobar.txt; 
 
#3. invoke the function: 
result=$(newest_file_matching_pattern /tmp/files_to_move "file*") 
printf "result: $result\n"

打印:

result: /tmp/files_to_move/file2.txt

或者,如果脆弱的 bash parlor 技巧分包到 python 解释器更适合您,这也是同样的事情:

#!/bin/bash 
 
function newest_file_matching_pattern { 
python - <<END 
import glob, os, re  
print(sorted(glob.glob("/tmp/files_to_move/file*"), key=os.path.getmtime)[0]); 
END 
} 
 
result=$(newest_file_matching_pattern) 
printf "result: $result\n" 

打印:

result: /tmp/files_to_move/file2.txt

【讨论】:

【解决方案6】:

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

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

那是那里使用的Schwartzian transform

【讨论】:

  • 愿施瓦茨与你同在!
  • 这个答案可能有效,但鉴于文档不佳,我不会相信它。
【解决方案7】:

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

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

如 cmets 所述,最好的跨平台解决方案可能是使用 Python、Perl、Ruby 脚本。

对于这样的事情,我倾向于使用 Ruby,因为它很容易编写小型、丢弃的脚本,但在命令行中具有 Python 或 Perl 的强大功能。

这是一颗红宝石:

ruby -e '
# index [0] for oldest and [-1] for newest
newest=Dir.glob("*").
    reject { |f| File.directory?(f)}.
    sort_by { |f| File.birthtime(f) rescue File.mtime(f) 
    }[-1]
p newest'

获取当前工作目录中的最新文件。

您还可以通过在glob 中使用**/* 或使用b2* 限制匹配文件等来使glob 递归

【讨论】:

  • 不。 "stat: cannot read file system information for '%m%t%N': No such file or directory"
  • 如果我没记错的话,我认为这可能适用于stat 的 Mac/FreeBSD 版本。要在其他平台上获得类似的输出,您可以使用 stat -c $'%Y\t%n' b2* | sort -rn | head -n1 | cut -f2-
  • 对于“其他平台”,您可能指的是 Linux。还有其他平台仍然需要不同的选项,或者在最坏的情况下,不容易提供对stat 行为的这种粒度控制。如果你需要一个可移植的解决方案,矛盾的是,也许可以编写一个 Perl 或 Python 脚本。
【解决方案8】:

对于谷歌员工:

ls -t | head -1

  • -t 按上次修改日期时间排序
  • head -1 只返回第一个结果

(Don't use in production)

【讨论】:

    【解决方案9】:

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

    find . -cmin 1 -name "b2*"
    

    此命令使用通配符搜索“b2*”查找恰好在一分钟前生成的最新文件。如果您想要最近两天的文件,那么最好使用以下命令:

    find . -mtime 2 -name "b2*"
    

    “。”表示当前目录。 希望这会有所帮助。

    【讨论】:

    • 这实际上并没有找到“最新的文件匹配模式”......它只是找到一分钟前创建的所有文件匹配模式,或者两天前修改。
    • 这个答案是基于提出的问题。此外,您可以调整命令以查看大约一天前出现的最新文件。这取决于你想做什么。
    • “调整”不是答案。这就像将其发布为答案:“只需调整 find 命令并根据您想要做的事情找到答案”。
    • 不确定不必要的评论。如果您觉得我的回答不成立,请提供适当的理由说明为什么我的回答对示例没有意义。如果不能这样做,请不要进一步评论。
    • 您的解决方案要求您知道何时创建了最新文件。那不在问题中,所以不,您的答案不是基于提出的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-24
    • 1970-01-01
    • 2017-07-31
    相关资源
    最近更新 更多