如何获取目录中的最后N个文件?

Sib*_*ing 15 shell filenames wildcards files

我在一个目录中有许多按文件名排序的文件。我希望将最后的 N 个(比如 N=4)个文件复制到我的主目录中。我该怎么做?

cp ./<the final 4 files> ~/

Joh*_*024 27

这可以通过 bash/ksh93/zsh 数组轻松完成:

a=(*)
cp -- "${a[@]: -4}" ~/
Run Code Online (Sandbox Code Playgroud)

这适用于所有非隐藏文件名,即使它们包含空格、制表符、换行符或其他困难字符(假设当前目录中至少有 4 个非隐藏文件带有bash)。

这个怎么运作

  • a=(*)

    这将创建一个a包含所有文件名的数组。bash 返回的文件名按字母顺序排序。(我假设这就是“按文件名排序”的意思

  • ${a[@]: -4}

    这将返回数组的最后四个元素a(假设数组包含至少 4 个带有 的元素bash)。

  • cp -- "${a[@]: -4}" ~/

    这会将最后四个文件名复制到您的主目录。

同时复制和重命名

这将仅将最后四个文件复制到主目录,同时将字符串添加a_到复制文件的名称:

a=(*)
for fname in "${a[@]: -4}"; do cp -- "$fname" ~/a_"$fname"; done
Run Code Online (Sandbox Code Playgroud)

从不同的目录复制并重命名

如果我们使用a=(./some_dir/*)而不是a=(*),那么我们就会遇到目录附加到文件名的问题。一种解决方案是:

a=(./some_dir/*) 
for f in "${a[@]: -4}"; do cp "$f"  ~/a_"${f##*/}"; done
Run Code Online (Sandbox Code Playgroud)

另一种解决方案是使用子shell和子shellcd中的目录:

(cd ./some_dir && a=(*) && for f in "${a[@]: -4}"; do cp -- "$f" ~/a_"$f"; done) 
Run Code Online (Sandbox Code Playgroud)

当子shell完成时,shell将我们返回到原始目录。

确保顺序一致

该问题要求“按文件名排序”的文件。Olivier Dulac 在评论中指出,该顺序会因地区而异。如果独立于机器设置的固定结果很重要,那么最好在a定义数组时明确指定语言环境。例如:

LC_ALL=C a=(*)
Run Code Online (Sandbox Code Playgroud)

您可以通过运行该locale命令找出您当前所在的语言环境。


jim*_*mij 9

如果您正在使用,zsh您可以将()一个所谓的glob 限定符列表括在括号中,这些限定符可以选择所需的文件。在你的情况下,那将是

cp *(On[1,4]) ~/
Run Code Online (Sandbox Code Playgroud)

这里On以相反的顺序按字母顺序对文件名进行排序,并且[1,4]只取其中的前 4 个。

您可以通过仅选择带有., 的纯文件(不包括目录、管道等),并附--加到cpcommand 以处理名称以-正确开头的文件,从而使其更加健壮,因此:

cp -- *(.On[1,4]) ~
Run Code Online (Sandbox Code Playgroud)

D如果您还想考虑隐藏文件(点文件),请添加限定符:

cp -- *(D.On[1,4]) ~
Run Code Online (Sandbox Code Playgroud)

  • 或者:`*([-4,-1])`。 (2认同)
  • @StéphaneChazelas 是的,让我们向未来的读者澄清,在 *normal* 方向(`on`)按字母顺序排序是默认行为,因此无需指定这一点,并且数字前面的 `-` 从底部开始计算文件名。 (2认同)

小智 7

这是使用极其简单的 bash 命令的解决方案。

find . -maxdepth 1 -type f | sort | tail -n 4 | while read -r file; do cp "$file" ~/; done
Run Code Online (Sandbox Code Playgroud)

解释:

find . -maxdepth 1 -type f
Run Code Online (Sandbox Code Playgroud)

查找当前目录中的所有文件。

sort
Run Code Online (Sandbox Code Playgroud)

按字母顺序排序。

tail -n 4
Run Code Online (Sandbox Code Playgroud)

只显示最后 4 行。

while read -r file; do cp "$file" ~/; done
Run Code Online (Sandbox Code Playgroud)

循环执行复制命令的每一行。


mik*_*erv 6

只要你觉得 shell 排序合适,你就可以这样做:

set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
cp "$@" /path/to/target/dir
Run Code Online (Sandbox Code Playgroud)

这与提供的bash-specific array 解决方案非常相似,但应该可以移植到任何 POSIX 兼容的 shell。关于这两种方法的一些注意事项:

  1. 重要的是你要引导你的cp论点,cp --或者你得到一个.点或/每个名字的开头。如果您不这样做,您将面临-cp的第一个参数中出现前导破折号的风险,该参数可以被解释为一个选项并导致操作失败或以其他方式呈现意外结果。

    • 即使使用当前目录作为源目录,这也很容易完成,如...set -- ./*array=(./*).
  2. 在使用这两种方法时,确保 arg 数组中至少有与您尝试删除的项目一样多的项目很重要 - 我在这里使用数学扩展来做到这一点。的shift如果有在阿根廷阵中至少4项只发生-然后只转移那些远离第一ARGS,使4盈余..

    • 因此,在某种set 1 2 3 4 5情况下,它会转移1离开,但在某种set 1 2 3情况下,它不会转移任何东西。
    • 例如:a=(1 2 3); echo "${a[@]: -4}"打印一个空行。

如果要从一个目录复制到另一个目录,可以使用pax

set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
pax -rws '|.*/|new_prefix|' "$@" /path/to/target/dir
Run Code Online (Sandbox Code Playgroud)

...这将sed在复制它们时对所有文件名应用-style 替换。