bash 命令创建包含目录中最近 10 张图像的数组?

bash 命令创建包含目录中最近 10 张图像的数组?

我正在编写一个 bash 脚本,我需要使用以下命令创建一个数组10 个最新图像文件(从新到旧)在当前目录中。

我认为“图像文件”是具有某些扩展名的文件,例如.jpg.png。我只需要支持一些特定的图像类型,我也可以用一个正则表达式来表达这一点,例如"\.(jpg|png)$".

我的问题是,如果我尝试这样做,例如$list=(ls -1t *.jpg *.png | head -10)生成的文件列表会以某种方式成为一个元素,而不是每个文件名都是数组中的单独元素。

如果我尝试使用$list=(find -E . -iregex ".*(jpg|png)" -maxdepth 1 -type f | head -10),我不确定如何按日期/时间对列表进行排序并仅保留文件名。另外 find 似乎放在./每个文件的前面,但我可以用sed.而且find我仍然存在整个列表成为$list数组中的一项的问题。

答案1

正确的语法是:

list=($(ls -t *.jpg *.png | head -10))
echo First element: ${list[0]}
echo Last element: ${list[9]}

但是,此解决方案在文件名包含空格字符(或一般的任何空格)时会出现问题。

答案2

对于bash≥ 4:

要将命令的输出逐行读取到数组中,您应该使用readarray

readarray files < <(ls -1t *.jpg *.png | head -10)

... 或者mapfile

mapfile -t files < <(ls -1t *.jpg *.png | head -10)

否则:

files=()
while IFS= read -r f; do
    files+=( "$f" )
done < <(ls -1t *.jpg *.png | head -10)

也可以看看


,文件名允许有换行符,因此为了读取文件名,您应该使用findand use \0delimiter 而不是ls -1which 使用\ndelimiter:

files=()
while IFS=  read -r -d $'\0' f; do
    files+=("$f")
done < <(
    find . -maxdepth 1 -type f \
      -regextype posix-extended -iregex ".*\.(jpg|png)$" \
      -printf '%T@\t%P\0' \
    | sort -nrz \
    | head -z -n 10 \
    | cut -z -f2-
)

答案3

如果 zsh 是一个选项,那么它会更简单:

set -o nocaseglob
array=( *.(png|jpg)(Om[-10,-1]) )

允许set -o nocaseglob更简单地png|jpg匹配大小写的变体,例如PNGJpG

下一条语句将非常具体的文件名生成 (glob) 的结果分配给一个数组。从左到右:

  • *.(png|jpg).jpg-- 扩展为以或结尾的文件名列表.png,具体取决于我们启用的区分大小写选项
  • (Om ...)-- 一个 zsh“glob 限定符”,表示O按修改时间(从最旧到最新)对文件进行排序(rder)
  • [-10,-1]-- 一个 zsh 数组拼接,它在末尾获取十个元素(最近的十个文件)

一旦您可以解析语法,zsh 就可以更轻松地处理此类情况,因为通配/文件名生成会为您处理文件名 - 无需担心解析ls。例如,使用我在我的文件中生成的“有趣”文件名其他答案,结果是:

$ print -l $array
4521.png
a?b.jpg
$( echo boom ).jpg
a*b.jpg
[x].jpg
X▒Y.jpg
single'quote.jpg
backslash.jpg
②.jpg
*.jpg

(排序结果略有不同,因为某些文件具有相同的时间戳)。

答案4

ls如果您可以依赖外部实用程序和 bash v4+(对于关联数组),则可以不解析,stat而是通过 inode 收集文件列表,然后收集最新 inode 的列表,然后构建文件名数组:

shopt -s nocaseglob extglob
declare -a filesbyinode=()
for f in *.@(jpg|png); do filesbyinode[$(stat -c %i "$f")]=$f; done
[ ${#filesbyinode[@]} -gt 0 ] || return
declare wantedfiles=()
for inodes in $(stat -c '%Y %i' *.@(jpg|png) | sort -k1,1rn | awk '{print $2}' | head -10)
do 
  wantedfiles+=("${filesbyinode[$inodes]}")
done
declare -p wantedfiles

第一步是设置两个 shell 选项:

  • nocaseglob-- 这使得通配符jpg也能够匹配JPG(and JpGand ...)
  • extglob-- 这允许使用以下方式:匹配的文件名可以以或@(jpg|png)结尾(取决于上面的)jpgpngnocaseglob

然后,我们设置一个空关联数组,通过索引节点对文件名进行索引。

随后的循环使用 inode 索引(命令的结果)和文件名作为值for构建数组。filesbyinodestat

如果没有文件,我们会根据return您的情况根据需要进行调整(可能是 if/else)。

然后,我们声明一个(常规)数组来保存我们感兴趣的文件。下一个for循环将迭代 10 个最新的 inode,并将相应的文件名添加到数组中。最近 10 个 inode 是通过扩展与之前相同的通配符来确定的,但仅询问修改时间(自纪元以来的秒数)和 inode;按字段 #1 中的修改时间(最大/最近的在前)排序后,我们将字段 #2 中的 inode 剥离出来,awk并获取其中的前 10 个 inode head

作为演示该代码对于各种文件名都是安全的:

for i in $(seq 1 10); do touch $RANDOM.jpg $RANDOM.png $RANDOM.txt; sleep 1.1; done
touch x.jpg '[x].jpg' 'a?b.jpg' 'a*b.jpg' '$( echo boom ).jpg' 
touch single\'quote.jpg double\"quote back\\slash.jpg '*.jpg' ②.jpg

...输出是:

declare -a wantedfiles=([0]="②.jpg" [1]="*.jpg" [2]="single'quote.jpg" [3]="back\\slash.jpg" [4]=$'X\240Y.jpg' [5]="[x].jpg" [6]="a?b.jpg" [7]="a*b.jpg" [8]="\$( echo boom ).jpg" [9]="25396.jpg")

$RANDOM(根据出现的内容调整最后一个文件名)。

相关内容