Bash:按文件名中的每个出生年份复制 n 个文件

Bash:按文件名中的每个出生年份复制 n 个文件

我有数千张照片,文件名中包含出生年份。我需要为每个出生年份查找并复制至少 100 个文件,比如说 2000 年出生的 100 个文件,2001 年出生的 100 个文件,...,依此类推。

这是文件名的格式:

35077502_1995-02-01_2012.jpg

我猜这张照片是2012年拍摄的。

可以使用bash脚本来完成吗?

谢谢

答案1

#!/bin/bash

IFS=$'\n' years=( $(find . -maxdepth 1 -name '*.jpg' -print0 | 
                    sed -zEn 's/^.*_([0-9][0-9][0-9][0-9])-.*\.jpg/\1/p' | 
                    tr '\0' '\n' | 
                    sort -u)
                )

for year in "${years[@]}" ; do
  mkdir -p "$year"
  find . -iname "*_${year}-*.jpg" -size +1k -print0 |
    head -z -n 100 |
    xargs -0r cp -t "$year"
done

这将构造一个数组 ( $years),其中包含从当前目录中的文件名中提取的唯一 4 位年份集合,其中年份前面是下划线 ( _),后面是破折号 ( -)。这需要akased选项的 GNU 版本。-z--null-data

对于每一年,它首先为该年创建一个目录(如果该目录尚不存在),然后用于find列出与所需模式匹配且大小大于 1 KB 的所有文件名。然后通过该列表head仅获取前 100 行,然后将xargs文件复制到适当的目录。

文件名列表在整个管道中以 NUL 结尾,以便它适用于所有有效的文件名(即,如果文件名中包含空格、制表符、换行符或其他不寻常但完全有效的字符,它不会中断)

这也需要GNU 版本head(这是 Linux 上的标准),因为它使用-z选项(也 --zero-terminated称为 NUL 终止输入)。具体来说,它需要一个版本更新于 2016 年 1 月 13 日。它还需要 GNU 的(又名)cp选项,该选项允许目标目录作为第一个参数而不是最后一个。-t--target-directory

如果需要对文件进行排序,则可以在和命令sort -z之间插入- 例如.这也需要 GNU 版本的.findheadfind ... -print0 | sort -z ... | head -z ...sort

这假设,如您问题的修订中所示,文件名有一个下划线,后跟年份作为.jpg扩展名之前的最后一个内容。

如果年份可以出现在文件名中的任何位置,您可能需要使用-iname "*${year}*.jpg"(不带下划线,并且在和*之间有一个秒),但要注意开头的八位数字类似于 的文件,其中包含子串。${year}.jpg604200172001

这还假设您的所有文件都有(不区分大小写).jpg扩展名(而不是.jpeg, .jpe, .jfif, .gif,.png等)。如果需要多个文件扩展名,-iregex可以使用该选项而不是-iname.

答案2

如果文件名中没有令人讨厌的东西,你可以做

for year in 2000 2001; do
  cp `ls *${year}*.jpg|head -n 100` destination
done

答案3

zsh

for y ({1995..2017}) (cp -- **/*_$y.jpg(.LK+1[1,100]) destination)
  • **/:在任何级别的子目录中,按字母顺序排序
  • .:仅限常规文件
  • LK+1:长度超过1KiB
  • [1,100]: 前一百个。

(由于排序顺序将决定复制哪些文件,因此您可能需要添加nglob 限定符以使排序为数字)。

或者避免对年份列表进行硬编码并多次抓取目录:

typeset -A files n
for f (**/*_<->.jpg(.LK+1)) {
  y=${${f##*_}%.*}
  ((++n[$y] > 100)) || files[$y]+=$f$'\0'
}
for y (${(k)files}) {
  mkdir -p $y && cp -- ${(0)files[$y]} $y
}

(未经测试)

相关内容