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


15

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

cp ./<the final 4 files> ~/


1
在下面的所有答案中,您可能需要在命令前使用范围添加“ LC_ALL = ...”,以便其范围为您使用正确的语言环境(语言环境可以更改,然后字符顺序可以更改例如,在某些语言环境中,[az]可以包含除“ Z”以外的所有字母,因为字母是有序的:aAbBcC .... zZ。存在其他变体(强调字母,甚至更特殊的顺序) )。通常的选择是:LC_ALL=C
Olivier Dulac 2015年

Answers:


23

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

a=(*)
cp -- "${a[@]: -4}" ~/

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

怎么运行的

  • a=(*)

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

  • ${a[@]: -4}

    这将返回数组的最后四个元素a(前提是该数组至少包含个带有的元素bash)。

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

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

同时复制和重命名

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

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

从其他目录复制并重命名

如果使用a=(./some_dir/*)而不是a=(*),则存在目录附加到文件名的问题。一种解决方案是:

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

另一个解决方案是使用一个子shell并cd进入该子shell 中的目录:

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

子外壳程序完成后,外壳程序将我们返回到原始目录。

确保顺序一致

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

LC_ALL=C a=(*)

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


9

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

cp *(On[1,4]) ~/

在此,On文件名以相反的顺序按字母顺序排序,并且[1,4]仅使用前4个。

通过使用选择仅纯文件(不包括目录,管道等).,并通过附加--cp命令以处理名称以-正确开头的文件,您可以使此功能更强大:

cp -- *(.On[1,4]) ~

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

cp -- *(D.On[1,4]) ~

2
或:*([-4,-1])
斯特凡Chazelas

2
@StéphaneChazelas是的,让我们为以后的读者澄清一下,按正常方向(on)按字母顺序排序是默认行为,因此无需指定,并且-数字前面从底部开始计算文件名。
jimmij 2015年

7

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

find . -maxdepth 1 -type f | sort | tail -n 4 | while read -r file; do cp "$file" ~/; done

说明:

find . -maxdepth 1 -type f

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

sort

按字母顺序排序。

tail -n 4

仅显示最后4行。

while read -r file; do cp "$file" ~/; done

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


6

只要您认为外壳排序合适,就可以这样做:

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

这与提供的bash特定于阵列的解决方案非常相似,但应可移植到任何POSIX兼容的外壳中。关于这两种方法的一些注意事项:

  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

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


4

如果只有文件且文件名不包含空格或换行符(并且您尚未修改$IFS)或全局字符(或某些带有的某些实现的不可打印字符ls),并且不以开头.,则可以执行此操作:

cp -- $(ls | tail -n 4) ~/

这称为命令替换,非常方便。tldp.org/LDP/abs/html/commandsub.html#CSPARENS
iyrin 2015年

谢谢!有用。是否可以快速a_为所有四个文件添加前缀?我试过了echo "a_$(ls | tail -n 4)",但是它只是添加了第一个文件。
西伯斯赌博

@SibbsGambling我的方法不适合该方法,但是John1024的答案很容易适应该方法。
Hauke Laging

5
通常,解析ls输出被认为是错误的形式,因为它通常会严重失败,因为文件名不仅包含空格,而且还包含制表符,换行符或其他有效但“困难”的字符。
HBruijn 2015年

2
@HaukeLaging即使当前这不是问题(因为我的目录中没有“复杂的” firename),我可能也有两年的时间,突然事情落在我的脚上……
glglgl 2015年

1

这是一个简单的bash解决方案,没有任何循环代码。将最后4个文件从当前目录复制到目标位置:

$ find . -maxdepth 1 -type f |tail -n -4|xargs cp -t "$destdir"

1
您如何确保find按所需顺序提供文件?如何确保名称中包含空格字符(包括换行符)的文件得到正确处理?
库萨兰达
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.