我有大约这种格式的 4k 图片:
photo_6923@06-01-2022_14-18-36.jpg
photo_6924@07-01-2022_00-03-23.jpg
photo_6925@07-01-2022_01-36-20.jpg
photo_6926@07-01-2022_10-44-20.jpg
photo_6927@07-01-2022_10-44-20.jpg
正如您所猜测的,@ 之后到下划线的第一个字符串对应于日期,从下划线到文件扩展名,再到时间。
不幸的是,秒数不显示,因此在给定分钟拍摄的照片将具有相同的结局;例如
photo_6925@07-01-2022_01-36-20.jpg
photo_6926@07-01-2022_10-44-20.jpg
photo_6927@07-01-2022_10-44-20.jpg
我想像这样重命名它们
(1) 删除
photo_*.*@
(2)更改文件的格式将“-”改为“_”
(3)将时间的格式改为_01h36m20s而不是_01-36-20
(4) 如果一个文件存在多个实例,不要删除它们,而是添加后缀,如
照片_6926@07-01-2022_10-44-20.jpg ---> 07_01_2022_01h36m20s_00001.jpg
照片_6927@07-01-2022_10-44-20.jpg ---> 07_01_2022_01h36m20s_00002.jpg
(5)最后一个问题:我有不同的文件夹,其中包含不同的图片,不幸的是,在解决第 1-4 点之后,不同的文件夹中可能有两张同名的图片,因此将它们移动到新文件夹会覆盖其中的一些图片。一个潜在的解决方案是继续增加后缀的计数器(这就是为什么我在那里添加了五个零)。这意味着
如果文件夹1和文件夹2中都存在名称A,则将文件夹1和文件夹3中的所有图片移动到新文件夹时,检查是否存在多个姿势。如果是这种情况,请将计数器加一,即想象以下情况
$ ls 1/ 2/ 3/
1/
07_01_2022_01h36m20s_00001.jpg
07_01_2022_01h36m20s_00002.jpg
2/
07_01_2022_01h36m20s_00002.jpg
3/
当移动它们时,
$ mv 1/* 2/* 3/,
ls 3/
07_01_2022_01h36m20s_00001.jpg
07_01_2022_01h36m20s_00002.jpg
07_01_2022_01h36m20s_00003.jpg <-------- counter added one here
谢谢! PS:可以使用元数据来做到这一点,但不幸的是所有元数据都已从图片中删除。
答案1
和zsh
:
autoload zmv
typeset -A n=()
# 1 23 4 5 6 7 8 9
zmv -n '(**/)photo_<->@((<->)-(<->)-(<->)_(<->)-(<->)-(<->))(.jpg)(#qn.)' \
'$1${3}_${4}_${5}_${6}h${7}m${8}s_${(l[5][0])$((++n[\$2]))}$9'
如果满意,请删除-n
(试运行)。在你的样本上,这给出了:
mv -- photo_6923@06-01-2022_14-18-36.jpg 06_01_2022_14h18m36s_00001.jpg
mv -- photo_6924@07-01-2022_00-03-23.jpg 07_01_2022_00h03m23s_00001.jpg
mv -- photo_6925@07-01-2022_01-36-20.jpg 07_01_2022_01h36m20s_00001.jpg
mv -- photo_6926@07-01-2022_10-44-20.jpg 07_01_2022_10h44m20s_00001.jpg
mv -- photo_6927@07-01-2022_10-44-20.jpg 07_01_2022_10h44m20s_00002.jpg
解释:
typeset -A n=()
创建一个A
最初为空的关联数组zmv
是一个可自动加载的函数,它需要一个全局模式(扩展的全局变量,zmv
确实extendedglob
在执行期间设置了该选项)并且表达作为两个单独的参数,并重命名与图案到扩大表达其中$1
,$2
... 对应于模式中相应的第n 对 所匹配的内容。(...)
- 对于模式:
**/
匹配任何级别的子目录(包括 0),并且该子目录位于第一个目录中,因此将在替换中(...)
可用。$1
请注意 zmv 处理文件深度优先(在它们所在的树枝之前离开)就好像被赋予了(#qod)
全局限定符这在重命名文件时通常很重要,但在这里并不重要,因为我们只重命名常规文件,而不是目录。<->
匹配任何正十进制整数。这就像<1-31>
但未指定边界,因此匹配<0-infinity>
或 IOW 任何十进制数字序列。(<1-31>)-(<1-12)-(<1900-2100>)...
如果您希望匹配更严格,可以将其更改为。(#q...)
添加 glob 限定符,n
以便排序为数字排序,而不是默认的词法排序(photo_10
因此后photo_2
例如而不是之前),并将.
匹配限制为常规的文件(不包括所有其他类型,例如目录、符号链接、fifos...)。
- 对于更换:
${(l[5][0])expansion}
l
5
eft用s 将扩展填充到字符长度0
。$(( ++n[\$2] ))
扩展为 key 的关联数组元素的值(与第二个, so$2
匹配)加一。请注意,延迟该参数的取消引用,因此它不会在算术表达式内扩展。例如,如果被包含(这里不是这种情况),这将是一个问题。(...)
((<->)-(<->)-(<->)_(<->)-(<->)-(<->))
\
$2
]
要根据文件名中的日期设置 EXIF CreateDate:
exiftool -r -ext jpg -d '%d-%m-%Y_%H-%M-%S' \
-if '$Filename =~ /@\d+-\d+-\d+_\d+-\d+-\d+\.jpg\z/i' \
-'CreateDate<${FileName;s/.*@//;s/\.jpg\z//i}' .
(与外壳无关)/
解释:
-r
:r
在作为参数传递的文件中以递归方式查找文件目录(此处为.
当前工作目录)。-ext jpg
:仅考虑带有扩展名的文件jpg
(不区分大小写)。-if 'perl expression'
:进一步将过滤器限制为那些Perl 表达式返回真。$Filename =~ /@\d+-\d+-\d+_\d+-\d+-\d+\.jpg\z/i
:文件名(不带目录部分)是否与给定的perl正则表达式匹配,因此这里以.结尾@<digits>-<digits>-<digits>_<digits>-<digits>-<digits>-.jpg
。-d '%d-%m-%Y_%H-%M-%S'
使用 strftime/strptime 模板对日期进行f
排序/排列。p
-'CreateDate<date'
:将CreateDate
EXIF 元数据属性设置为指定日期,此处:${FileName;s/.*@//;s/\.jpg\z//i}
我们已经删除了最右边的所有内容的文件名@
和.jpg
扩展名。
答案2
我已经探索了一个解决方案,使用TXR口齿不清。目前的状态有点冗长。它将名称解析为结构。
对于可能存在多个目录而导致名称冲突的要求,我们应该同时处理所有目录。我准备了这个示例路径集:
path/a/photo_6923@06-01-2022_14-18-36.jpg
path/a/photo_6924@07-01-2022_00-03-23.jpg
path/a/photo_6925@07-01-2022_01-36-20.jpg
path/a/photo_6926@07-01-2022_10-44-20.jpg
path/a/photo_6927@07-01-2022_10-44-20.jpg
path/to/b/photo_6923@06-01-2023_14-18-36.jpg
path/to/b/photo_6924@07-01-2023_00-03-23.jpg
path/to/b/photo_6925@07-01-2023_01-36-20.jpg
path/to/b/photo_6926@07-01-2023_10-44-20.jpg
path/to/b/photo_6927@07-01-2022_10-44-20.jpg
我们在一个path/a
目录和一个path/to/b
目录中都有名字。存在具有相同时间/日期的冲突条目07-01-2022_10-44-20
。
在实际程序中,我们将使用一些 glob 表达式来获取名称,例如:
(glob "{path/a,path/to/b}/photo_*.jpg")
而不是从文件中读取,我们可以dummy-rename
用rename-path
.
跑步:
$ txr rename.tl
path/to/b/photo_6926@07-01-2023_10-44-20.jpg -> path/to/b/07_01_2023_10h44m20s
path/a/photo_6926@07-01-2022_10-44-20.jpg -> path/a/07_01_2022_10h44m20s_00000
path/a/photo_6927@07-01-2022_10-44-20.jpg -> path/a/07_01_2022_10h44m20s_00001
path/to/b/photo_6927@07-01-2022_10-44-20.jpg -> path/to/b/07_01_2022_10h44m20s_00002
path/to/b/photo_6924@07-01-2023_00-03-23.jpg -> path/to/b/07_01_2023_00h03m23s
path/a/photo_6924@07-01-2022_00-03-23.jpg -> path/a/07_01_2022_00h03m23s
path/to/b/photo_6925@07-01-2023_01-36-20.jpg -> path/to/b/07_01_2023_01h36m20s
path/a/photo_6925@07-01-2022_01-36-20.jpg -> path/a/07_01_2022_01h36m20s
path/to/b/photo_6923@06-01-2023_14-18-36.jpg -> path/to/b/06_01_2023_14h18m36s
path/a/photo_6923@06-01-2022_14-18-36.jpg -> path/a/06_01_2022_14h18m36s
代码在rename.tl
:
(defstruct name ()
orig dir number time
(:method fmt (me)
(let ((tm me.time))
`@{tm.month}_@{tm.day}_@{tm.year}_@{tm.hour}h@{tm.min}m@{tm.sec}s`)))
(defun parse (str)
(let ((dir (dir-name str))
(base (base-name str)))
(match `photo_@num\@@mm-@dd-@{yyyy}_@HH-@[email protected]` base
(new name orig str dir dir number num
time (new time year yyyy month mm day dd
hour HH min MM sec SS)))))
(defun dummy-rename (from dir to)
(put-line `@from -> @(path-cat dir to)`))
(flow (file-get-lines "data")
(mapcar parse)
(group-by .time)
(dohash (date part @1)
(if (eql 1 (len part))
(let ((n (first part)))
(dummy-rename n.orig n.dir n.(fmt)))
(each ((n part)
(i "00000".."99999"))
(dummy-rename n.orig n.dir `@{n.(fmt)}_@i`)))))
只有与相同日期/时间冲突的文件才会获得递增标记。由于这是跨多个目录的,因此我们不需要五位数字。
该算法的核心是获取解析的对象并根据日期将它们分组。对于那些仅包含一个元素的组,我们格式化名称而不使用附加计数器。在两个或更多的组中,我们迭代它们,同时并行迭代 00000 到 99999,并将其作为后缀。
重命名未排序,因为group-by
会生成哈希表。我们可以通过以下方式更好地了解组织sort
:
$ txr rename.tl | sort
path/a/photo_6923@06-01-2022_14-18-36.jpg -> path/a/06_01_2022_14h18m36s
path/a/photo_6924@07-01-2022_00-03-23.jpg -> path/a/07_01_2022_00h03m23s
path/a/photo_6925@07-01-2022_01-36-20.jpg -> path/a/07_01_2022_01h36m20s
path/a/photo_6926@07-01-2022_10-44-20.jpg -> path/a/07_01_2022_10h44m20s_00000
path/a/photo_6927@07-01-2022_10-44-20.jpg -> path/a/07_01_2022_10h44m20s_00001
path/to/b/photo_6923@06-01-2023_14-18-36.jpg -> path/to/b/06_01_2023_14h18m36s
path/to/b/photo_6924@07-01-2023_00-03-23.jpg -> path/to/b/07_01_2023_00h03m23s
path/to/b/photo_6925@07-01-2023_01-36-20.jpg -> path/to/b/07_01_2023_01h36m20s
path/to/b/photo_6926@07-01-2023_10-44-20.jpg -> path/to/b/07_01_2023_10h44m20s
path/to/b/photo_6927@07-01-2022_10-44-20.jpg -> path/to/b/07_01_2022_10h44m20s_00002
我们可以清楚地看到,在第二个目录中,名称避免了冲突07_01_2022_10h44m20s_00002
。
答案3
使用 GNU 并行:
parallel mv -- {} '{=s/(.*)photo_\d+\@((..)-(..)-(....)_(..)-(..)-(..))(.jpg)/$_="$1$3_$4_$5_$6h$7m$8s_".sprintf("%05d",++$n{$2}).$9/e =}' ::: dira/* dirb/*
所有文件名现在都是唯一的,并且可以移动到同一文件夹中。