我的目录中有大约 100000 个没有扩展名的文件,我不知道它们是什么,有些似乎是 pdf、jpg、png、xls 等...
有没有一种简单的方法来为每个文件添加扩展名?
我当前的想法是制作一个脚本,为每个文件创建一个file
命令,使用带有 if 的 grep (如 grep png)获取结果,如果为真则添加 png。
如果谁有更好的方法。
问候
答案1
不容易。file(1)
有一个--extension
选项,但在我的测试(Debian/bullseye)中,大多数文件显示问号(???
)。
并且case..esac
为每种已知文件类型进行编码很可能无法正常工作,因为file
似乎检测到至少 3000 个文件类型:
❯ file -l | wc -l
3186
首先,我尝试获取给定目录中的文件类型列表:
笔记,zsh 通配符此处使用:
❯ file -p -n -b .*(.) *(.) | sort | uniq
ASCII text
ASCII text, with no line terminators
ASCII text, with very long lines
ASCII text, with very long lines, with no line terminators
data
empty
JPEG image data, JFIF standard 1.01, resolution (DPI), density 300x300, segment length 16, Exif Standard: [TIFF image data, big-endian, direntries=0], baseline, precision 8, 96x96, components 3
JSON data
Non-ISO extended-ASCII text, with very long lines, with CRLF, LF line terminators, with escape sequences
Python script, UTF-8 Unicode text executable
UTF-8 Unicode text
UTF-8 Unicode text, with very long lines
very short file (no magic)
X11 Xauthority data
以下find
行会产生相同的输出:
find -maxdepth 1 -type f -exec file -p -n -b {} \; | sort | uniq
通过此列表,我将创建一种文件类型扩展名映射/查找文件并使用它来重命名文件:
ext1;ASCII text
ext2;ASCII text, with no line terminators
ext3;ASCII text, with very long lines
ext4;ASCII text, with very long lines, with no line terminators
ext5;data
ext6;empty
ext7;JPEG image data, JFIF standard 1.01, resolution (DPI), density 300x300, segment length 16, Exif Standard: [TIFF image data, big-endian, direntries=0], baseline, precision 8, 96x96, components 3
ext8;JSON data
ext9;Non-ISO extended-ASCII text, with very long lines, with CRLF, LF line terminators, with escape sequences
ext10;Python script, UTF-8 Unicode text executable
ext11;UTF-8 Unicode text
ext12;UTF-8 Unicode text, with very long lines
ext13;very short file (no magic)
ext14;X11 Xauthority data
华泰
答案2
使用 perl
-basedrename
和File::MimeInfo::Magic
perl 模块(在libfile-mimeinfo-perl
基于 Debian 的系统上的软件包中,通常在桌面安装中默认安装,因为它是 的依赖项(推荐)xdg-utils
):
rename -n -- '
use File::MimeInfo::Magic qw(mimetype extensions);
my $ext; $_ .= ".$ext" if ! /\./ && ($ext = extensions mimetype$_)' *
(-n
如果看起来正确,请删除空运行)。
答案3
此答案假设您有一个相对干净的文件名列表,全部位于一个目录中,并且文件名不包含空格、制表符、换行符和其他不良字符。此处的代码片段适用于bash
shell。
首先,做好备份
每当您对数千个文件进行自动重命名时,总会有可能出现问题。一个问题乘以十万个文件等于需要修复十万个问题才能重试。
首先进行备份:
$ mkdir ~/my-backup
$ rsync -av ./ ~/my-backup/
现在,如果您出现任何问题,您可以从备份中恢复:
$ rsync -av --delete ~/my-backup/ ./
file
变化很大
该命令的实施质量file
因发行版而异。可以肯定的是,版本越新越好,因为使用的magic
文件file
可能会更新。如果可以的话,通过将rsync
文件复制/复制到运行具有更好版本的file
.
那么我的系统的实施效果如何file
?
让我们定义一个函数,该函数将在我们提供的任何通配符 glob 上fext
运行。此外,我们将通过一些简单的转换file -b --extension
来运行输出,以根据我们的喜好标准化输出:file
sed
file
fext () {
file -b --extension "$@" |
sed -e 's-^jpeg/jpg/jpe/jfif$-jpg-' |
sed -e 's-^pdf$-PDF-' |
cat
}
没用的请见谅cat
。它是为了模块化而包含的,因此,如果您的文件类型返回多个扩展名字符串,您可以复制、粘贴和编辑该sed
行,将这些倍数转换为您喜欢的单个扩展名,或者根据需要任意大写,等等。在此示例中,由file
as标识的文件jpeg/jpg/jpe/jfif
将具有扩展名jpg
,而由 as 标识的文件pdf
将具有扩展名PDF
。它cat
只是转换列表末尾的一个无操作占位符。
具有多个扩展名的其他文件类型又如何呢?
确保您已枚举集合中file
返回多个扩展名的所有可能的文件类型非常重要。这很容易测试:
$ fext * | grep /
一定没有输出。如果有输出,您需要在定义sed
中添加另一行fext
。
现在,您可以fext
对整个文件集合进行运行,以查看它认为可以识别的文件数量以及无法识别的文件数量。
我正在使用由 152 个弱选择文件组成的测试组。我在三个系统上运行:
$ fext * | sort | uniq -c
Ubuntu 18.04.2 LTS:
$ fext * | sort | uniq -c
137 ???
15 jpg
FreeBSD 13.1:
$ fext * | sort | uniq -c
28 ???
76 PDF
15 jpg
32 png
1 tif,tiff
Ubuntu 22.04 LTS:
$ fext * | sort | uniq -c
28 ???
15 jpg
76 PDF
32 png
1 tif,tiff
magic
请注意,我们在该实用程序使用的文件中发现了一个错误file
。幸运的是,这在我们的函数中很容易修复fext
:
$ fext () {
file -b --extension "$@" |
sed -e 's-^jpeg/jpg/jpe/jfif$-jpg-' |
sed -e 's-^pdf$-PDF-' |
sed -e 's-^tif,tiff$-tiff-' |
cat
}
$ fext * | sort | uniq -c
28 ???
15 jpg
76 PDF
32 png
1 tiff
出色的。这里重要的是没有斜杠(或逗号!)。我们创建了一个粗略的定量指标,使我们能够了解由file
.具体来说,file
无法识别 152 个文件中的 28 个,即略高于 18%。我们可以进一步完善我们的函数,为无法识别fext
的类型的文件分配默认扩展名。file
$ fext () {
file -b --extension "$@" |
sed -e 's-^jpeg/jpg/jpe/jfif$-jpg-' |
sed -e 's-^pdf$-PDF-' |
sed -e 's-^tif,tiff$-tiff-' |
sed -e 's-^???$-unknown-' |
cat
}
$ fext * | sort | uniq -c
15 jpg
76 PDF
32 png
1 tiff
28 unknown
如果我们愿意,我们可以列出并查看 无法识别的grep
文件的具体文件名:file
$ for f in *; do [ -f "$f" ] && printf '%10s %s\n' "$(fext "$f")" "$f"; done | grep -w unknown
在我的(弱)样本集中,“未知”文件主要是 PostScript 文件。 file
确实如此识别它们,但该magic
文件没有为 PostScript 文件指定扩展名。
因此,如果我们fext
为每个文件提供扩展名,则无法识别的文件file
将收到扩展名unknown
.
$ for f in *; do [ -f "$f" ] && mv -vi "$f" "$f.$(fext "$f")"; done
答案4
不幸的是,file -b --extension
这不是很有帮助,因为它返回???
许多具有已知扩展名的格式。相反,首先克隆感兴趣的目录,以防出现任何意外:
# using hard links
cp --link /path/to/originals /path/to/files
# using reflinks on supported file systems
cp --reflink=auto /path/to/originals /path/to/files
然后在目录中创建 mime 类型列表:
find /path/to/files -type f -exec file -b --mime-type {} \; | sort -u
输出看起来像:
application/epub+zip
application/msword
application/pdf
application/zip
image/gif
image/jpeg
image/png
image/svg+xml
text/html
text/plain
text/rtf
text/xml
现在创建一个bash
脚本add_ext.sh
,根据 mime 类型(如果需要)使用适当的扩展名重命名文件。
#!/usr/bin/env bash
[ $# -lt 1 ] && echo "Usage: $0 <FILE>" && exit 1
mimetype=$(file -b --mime-type "$1")
extension="${1#**.}"
new_ext="unset"
case $mimetype in
'application/epub+zip')
new_ext="epub"
;;
'application/msword')
new_ext="doc"
;;
'application/pdf')
new_ext="pdf"
;;
'application/zip')
new_ext="zip"
;;
'image/gif')
new_ext="gif"
;;
'image/jpeg')
new_ext="jpeg"
;;
'image/png')
new_ext="png"
;;
'image/svg+xml')
new_ext="svg"
;;
'text/html')
new_ext="html"
;;
'text/plain')
new_ext="txt"
;;
'text/rtf')
new_ext="rtf"
;;
'text/xml')
new_ext="xml"
;;
esac
[ "$new_ext" != "unset" ] \
&& [ "$extension" != "$new_ext" ] \
&& [ ! -e "$1.$new_ext" ] \
&& mv "$1" "$1.$new_ext"
chmod +x add_ext.sh
在使用find
文件运行脚本之前不要忘记:
find /path/to/files -type f -exec ./add_ext.sh {} \;