我正在编写一个 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)
但,文件名允许有换行符,因此为了读取文件名,您应该使用find
and use \0
delimiter 而不是ls -1
which 使用\n
delimiter:
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
匹配大小写的变体,例如PNG
或JpG
。
下一条语句将非常具体的文件名生成 (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
(andJpG
and ...)extglob
-- 这允许使用以下方式:匹配的文件名可以以或@(jpg|png)
结尾(取决于上面的)jpg
png
nocaseglob
然后,我们设置一个空关联数组,通过索引节点对文件名进行索引。
随后的循环使用 inode 索引(命令的结果)和文件名作为值for
构建数组。filesbyinode
stat
如果没有文件,我们会根据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
(根据出现的内容调整最后一个文件名)。