首先,做好备份

首先,做好备份

我的目录中有大约 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-basedrenameFile::MimeInfo::Magicperl 模块(在libfile-mimeinfo-perl基于 Debian 的系统上的软件包中,通常在桌面安装中默认安装,因为它是 的依赖项(推荐)xdg-utils):

rename -n -- '
  use File::MimeInfo::Magic qw(mimetype extensions);
  my $ext; $_ .= ".$ext" if ! /\./ && ($ext = extensions mimetype$_)' *

-n如果看起来正确,请删除空运行)。

答案3

此答案假设您有一个相对干净的文件名列表,全部位于一个目录中,并且文件名不包含空格、制表符、换行符和其他不良字符。此处的代码片段适用于bashshell。

首先,做好备份

每当您对数千个文件进行自动重命名时,总会有可能出现问题。一个问题乘以十万个文件等于需要修复十万个问题才能重试。

首先进行备份:

$ mkdir ~/my-backup
$ rsync -av ./ ~/my-backup/

现在,如果您出现任何问题,您可以从备份中恢复:

$ rsync -av --delete ~/my-backup/ ./

file变化很大

该命令的实施质量file因发行版而异。可以肯定的是,版本越新越好,因为使用的magic文件file可能会更新。如果可以的话,通过将rsync文件复制/复制到运行具有更好版本的file.

那么我的系统的实施效果如何file

让我们定义一个函数,该函数将在我们提供的任何通配符 glob 上fext运行。此外,我们将通过一些简单的转换file -b --extension来运行输出,以根据我们的喜好标准化输出:filesedfile

fext () {
  file -b --extension "$@" |
  sed -e 's-^jpeg/jpg/jpe/jfif$-jpg-' |
  sed -e 's-^pdf$-PDF-' |
  cat
}

没用的请见谅cat。它是为了模块化而包含的,因此,如果您的文件类型返回多个扩展名字符串,您可以复制、粘贴和编辑该sed行,将这些倍数转换为您喜欢的单个扩展名,或者根据需要任意大写,等等。在此示例中,由fileas标识的文件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 {} \;

相关内容