0.脚本想要做这样的事情。

0.脚本想要做这样的事情。

这个问题是由我在一本 Linux 杂志上看到的一个简短脚本引发的。为了证明这不是我编造的,下面是它的一张图片:

相当糟糕的代码示例

我想写一封信给这份出版物的编辑,告诉他们这篇文章有什么问题以及如何写得更好。

该脚本尝试将 jpeg 文件捕获到变量中,以便lepton可以对它们进行某些操作(使用压缩)。

for jpeg in `echo "$(file $(find ./ ) |
   grep JPEG | cut -f 1 -d ':')"`
  do
     /path/to/command "$jpeg"
...

显然,在这种情况下,我们不能相信文件以.jpg扩展名命名,所以我们不能用类似这样的方法捕获它们

for f in *.JPG *.jpg *.JPEG *.jpeg ; do ...

因为作者曾经file检查过它们的类型,但是如果文件名不可信并且没有合理的扩展名,那么我就不明白我们如何能相信它们不存在-rf *换行符(; \ $!|或者其他什么东西。

我怎样才能明智地将文件捕获到变量中按类型使用for或,或者也许通过使用或其他方法while来避免这样做?find-exec

对图片中代码错误的洞察和演示可获得奖励。

我用 [bash] 标记了这个问题,因为它是关于一个 bash 脚本,但是如果你想用一种不使用 bash 的方式来回答,那么请随意这样做。

答案1

0.脚本想要做这样的事情。

问题中显示的脚本尝试枚举文件并检查它们是否为 JPEG,但无法可靠地完成。它尝试传递所有路径file一次运行中,从 的输出中提取文件名和类型file,其中是合理的因为这可能比file对每个文件一次又一次地运行更快。但要正确执行此操作,您需要注意如何将路径传递给file,如何file分隔其输出以及如何使用该输出。您可以使用这个:

#!/bin/bash

find . -exec file --mime-type -r0F '' {} + | while read -rd ''; do
    read -r mimetype
    case "$mimetype" in image/jpeg)
        # Bash placed the filename in "$REPLY" -- put commands that use it here.
        # You can have as many commands as you want before the closing ";;" token.
        ;;
    esac
done

这是几种正确的方法之一。(确实如此不是需要设置IFS=;见下文。)find+多个路径参数传递给file并只运行它处理所有参数所需的次数,通常只运行一次。归功于藝術本身为了传递--mime-typefile获取 MIME 类型,它包含您真正想要的信息并且易于解析。

以下是详细解释。我以 JPEG 压缩这一特定任务为例。这就是您展示的脚本的用途,lepton在决定如何改进该脚本时,应该考虑一些奇怪之处。如果您只想查看lepton在每个 JPEG 文件上运行的脚本,您可以跳至第节7. 综合起来

期限小路有多种定义。在这个答案中我用它来表示路径名

1. 安装lepton

您展示的脚本旨在遍历目录层次结构,查找 JPEG 图像,并使用无损 JPEG 压缩器对其进行处理lepton。对于您问题的主要动机,命令可能并不重要,但不同的命令具有不同的语法。有些命令在一次运行中接受多个输入文件名。大多数接受--以指示选项的结束。我将使用它lepton作为我的例子。该lepton命令不接受多个输入文件名并且不识别--

要使用lepton,请先安装它。它的针对 Ubuntu 17.04 及更高版本进行了官方打包sudo apt install lepton)。对于较早的 Ubuntu 版本,或要使用比您的版本打包的版本更新的版本,请克隆它的git存储库git clone https://github.com/dropbox/lepton.git)并构建源代码按照 README 中的说明进行操作.或者你也可以查找 PPA

取决于您的安装方式,lepton可能位于/usr/bin/usr/local/bin或其他地方。您可能希望将其放置在$PATH; 然后你可以将其作为 运行lepton。你展示的脚本使用绝对路径lepton标准实用程序mvrm, 但不是其他标准实用程序filefindgrepcut。(这是 Bash,因此echo--无论如何,在那个脚本中毫无意义--是 shell 内置的exit始终是内置的)虽然这不是剧本的严肃的缺陷,没有明显的原因导致这种不一致。除非你正在编写一个脚本来容忍没有$PATH合理设置——在这种情况下你必须对所有外部命令使用绝对路径——我建议对标准命令和你安装的命令使用相对路径。

2.跑步lepton

注意事项和一般信息

我使用 lepton v1.0-1.2.1-104-g209463a(来自 Git)进行了测试lepton2016 年 7 月发布所以我猜当前的语法将继续有效。但未来版本可能会添加功能。如果你几年后才读到这篇文章,你可能会检查它是否lepton增加了对曾经需要脚本的任务的支持。

注意传递的命令行参数。例如,我尝试将lepton用作-verbose第一个参数和art.jpg第二个参数。它将其解释-verbose为输入文件名并退出并显示错误,但在截断之前art.jpg(它将其解释为输出文件名)将其截断为零字节。幸运的是,我有备份!

您可以将零个、一个或两个路径传递给lepton。在所有情况下,它都会检查其输入文件或流以查看其是否包含 JPEG 或 Lepton 数据。JPEG 被压缩为 Lepton;Lepton 被解压缩为 JPEG。lepton将删除和添加文件扩展名,但不会使用它们来决定要做什么。

零文件名——lepton -读取自标准输入并写信给标准输出

因此,有一种读取方法lepton - < infile > outfileinfile并写信给outfile,即使它们的名称以 开头-(就像选项一样)。但我将使用的方法传递以 开头的路径.,因此我不必担心这一点。

一个文件名 —读取lepton infileinfile并命名其自己的输出文件。

这就是您展示的脚本的使用方式lepton

如果内容infile看起来像 JPEG,lepton则输出 Lepton 文件;如果其内容看起来像 Lepton 文件,lepton则输出 JPEG。lepton通过从中剥离扩展名来决定如何命名其输出文件infile,如果有的话,并根据所创建的文件类型添加.jpg或扩展名。但它确实.lep不是使用正在删除的扩展名(如果有)来推断其正在操作的文件类型。

它认为最后的 .以及其后的任何内容作为扩展。如果infilea.b.c,则得到a.b.lepa.b.jpg。如果文件名以 a 开头,.没有其他.s,则lepton 仍然将其视为扩展:从名为 的 JPEG中.abc,您可以获得.lep。只有.在文件名(而不是目录名)中才会触发此操作,因此从 Lepton 文件中,x/fo.o/abc您可以获得x/fo.o/abc.jpg(您想要的),而不是x/fo.jpg(这很糟糕)。

如果通过这种方式获得的输出文件名命名了一个现有文件,_则会在扩展名后末尾添加 s,直到没有 s,并使用添加下划线的名称:,,,,abc.lep等等,,,,等等。abc.lep_abc.lep__xyz.jpgxyz.jpg_xyz.jpg__

当您的文件以合理的方式命名时,这种方法最有效。

自动删除和添加扩展名以及添加下划线可以避免您自己处理的问题——在输出文件已经存在的情况下防止数据丢失。但它也暴露了什么可能您展示的脚本存在一个很深的设计缺陷。如果您的文件命名合理,则所有 JPEG 文件都以.jpg或结尾.jpeg(可能大写),并且没有非 JPEG 文件以 或 结尾。但是您不必检查文件以file找出哪些是 JPEG!

因此,您展示的脚本的前提是文件也许不会合理命名。脚本在包含空格、*和其他特殊字符的文件名上出现错误或意外行为总是很糟糕的。因此,它在空格上拆分和扩展 glob 的行为(外部未加引号的命令替换,旨在拆分单独的文件名,就是这样)尤其糟糕。请参阅Byte Commander 的精彩回答了解详情。这可能是你展示的剧本中最严重的缺陷。

但也值得考虑的是,如果文件名的最后一个字符.没有概念上开始文件扩展名。假设Pictures有四个文件,都是 JPEG:01. Milan wide-angle sunset、、和。然后01. Milan wide-angle sunset highres创建、、和——可能不是您想要的。02. Kyle birthday party prep - blooper cakes03. The subtle found art of unopened expired paint cans with peeling labelsfor f in ~/Pictures/0*; do lepton "$f"; done01.lep01.lep_02.lep03.lep

如果您有未命名的 JPEG.jpg或可能.jpeg,最好的一般方法是按此方式重命名它们,并调查这样做时出现的任何命名冲突。但这超出了本答案的范围。

这些重命名问题发生在不像 JPEG 那样命名的 JPEG 上,不是像 JPEG 一样命名的非 JPEG 文件。但即便如此,也可能有更好的解决方案。如果问题出._在 macOS 中的文件,而您不想删除它们,只需排除以 开头的文件._(甚至是以 开头的文件.)。不过,只传递一个路径可以避免lepton数据丢失(由于其_附加规则);如果主要的目标是排除非 JPEG,虽然实现上需要修复,但基本思想是合理的。

所以我将使用单路径语法lepton infile。但是任何考虑lepton对奇怪命名的文件进行此类自动化处理的人都应该记住,生成的.lep文件的命名方式可能不会透露输入文件名。

两个文件名 —完全符合您的预期。lepton infile outfile

但仅仅因为你期望它,并不意味着它就是正确的事。

与其他运行方式一样leptonlepton确定infile是要压缩的 JPEG 文件还是要解压缩的 Lepton 文件,方法是检查其内容。如果infile是 JPEG,lepton写入名为 Lepton 的文件outfile; 如果infile是一个 Lepton 文件,lepton写入一个名为outfile。使用此双路径语法,lepton不会以任何方式更改您指定的输出文件名。它不会添加或删除扩展名或附加_s 来解决命名冲突。如果outfile已经存在,将被覆盖。

您可能想要这样,但如果不想,并且您使用这种语法,那么您必须自己解决问题,方法是让脚本调整输出文件名。lepton当只使用一个路径参数运行时,您可能能够以比自己的方案更好的方式执行此操作。但我不会试图猜测您的具体需求和偏好;我只会使用单路径语法。

3. 将多条路径从 传递findfile

您展示的脚本尝试通过运行来file $(find ./ )传递每个参数的一个路径filefind命令替换。这通常不起作用,因为$(find ./ )文件名中可能包含空格。文件(尤其是图像!)和文件夹的名称中通常包含空格。您展示的脚本将路径./abc/foo bar.jpg视为两个路径,./abc/foobar.jpg。在最好的情况下,两者都不存在;如果它们存在,您会无意中操作错误的东西。并且原始路径根本不会被处理。

IFS=$'\n'虽然可以通过设置来减少这个问题的严重程度单词拆分仅在行与行之间执行(\n代表新队字符),这不是一个好的解决方案。除了不方便之外,它仍然会失败,因为文件和目录名称可能包含换行符。我建议不要用它们命名文件或目录,除非是为了测试程序或脚本是否有错误。但可以创建这样的名称,包括意外地文件名中不能包含的字符只有路径分隔符/空字符。因此,空字符是唯一不能出现在路径中的字符,也是分隔任意路径列表的唯一安全选择。这就是为什么find-print0动作和xargs-0选项的原因。

可以使用 正确完成此操作find . -print0 | xargs -0 ...,但您不需要第三个实用程序将路径从 传递findfilefind-exec操作就足够了。-exec构建要运行的命令之后的参数,直到\;+find ... -exec ... ;为每个文件运行一次命令,而find ... -exec ... +每次运行该命令时传递尽可能多的路径,这通常更快。通常所有参数都适合,命令只运行一次。在极少数情况下,命令行太长并且find运行命令不止一次。因此,该+形式仅适用于运行(A)最后采用路径参数并(二)在一次运行中使用多个文件名的工作方式与在单独运行中使用多个文件名的工作方式相同。

lepton是命令的示例一定不+不能使用的形式运行-exec,因为它不接受多个源文件名。第一个是输入,第二个是输出,其他的就太多了。但许多命令使用多个参数运行一次和使用一个参数运行多次执行相同的操作,file其中之一

此命令将生成表:

find . -exec file --mime-type -r0F '' {} +

find{}在调用时用路径替换参数file,并用+尽可能多的其他路径参数替换。

--mime-type -r0F ''传递给的选项find解释如下。

有些人引用 {},例如,'{}'这样做没问题,但 Bash 和其他 Bourne 风格的 shell 都不需要它。Bash 和一些其他 shell 支持括号扩展,但一对空括号不会展开。我选择不是引用{},因为人们误解引用{}会妨碍find执行单词拆分。即使你的 shell 需要{}引用,这也与分词无关,因为findnever 会这样做。(如果你想要分词,你必须告诉findshell -exec。)并且find无法判断你是否写了{}'{}'——shell 变成'{}'{}(在删除引文) 然后将其传递给find

4. 使用以下代码生成可用的⟨Path, File Type⟩表file

问题

file我必须将一些选项传递给-- 而不能直接使用-- 的原因是默认生成的find . -exec file {} +表不明确:file

01. Milan wide-angle sunset:                  JPEG image data, JFIF standard 1.01, resolution (DPI), density 1x1, segment length 16, baseline, precision 8, 1400x1400, frames 3
02. Kyle birthday party prep - blooper cakes: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 512x512, frames 3
first line
second line:                       JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 500x500, frames 3

这三行看起来像四行;一个文件名包含换行符。文件名也可以包含冒号,因此文件名的结尾并不总是很清楚。可能出现比上面显示的更令人困惑的例子。

描述栏中的信息也比我们需要的多得多。Byte Commander 解释grep每一行中的一个原因JPEG都会返回错误的结果:名称中包含的非 JPEG 文件JPEG会产生误报。(检查类型的重点是你不能依赖名称,所以这是你展示的脚本中的一个相当自相矛盾的错误。)但即使你知道你正在查看描述列,它仍然可能包含JPEG即使不是这种类型:

$ touch empty.JPEG  # not a JPEG
$ gzip -k empty.JPEG
$ file empty.JPEG*
empty.JPEG:    empty
empty.JPEG.gz: gzip compressed data, was "empty.JPEG", last modified: Mon Aug 28 16:37:56 2017, from Unix

字节指挥官的回答解决了这个问题(A)将选项传递-bfile,使其省略:类型前面的路径、分隔符和空格,然后(二)用于grep检查描述开始JPEG^ 在模式中^JPEG image data,这样做)。如果您跟踪传递给的路径,这将有效file- 这对 Byte Commander 的方法来说不是问题,因为它对file每个路径分别运行。

解决方案

我必须使用不同的解决方案,因为我的目标是解析两者路径类型来自file的输出,这样就file不需要为每个文件单独运行。幸运的是file在 Ubuntu 中有很多选项。 我用:file --mime-type -r0F '' paths

  • --mime-type打印一个MIME 类型而不是详细描述。这就是我所需要的,然后我就可以对整个内容进行精确匹配。对于 JPEG,file --mime-type显示image/jpeg在描述栏中。(另请参阅αғsнιη 的回答
  • 根据man file-r导致无法打印的字符不会被替换为八进制转义符,如\003。我相信否则我需要添加一个步骤来将此类序列转换回实际字符​​,这可能不能可靠地完成——如果这样的序列确实出现在文件名中会怎么样?(file 没有转义\\\。)我说“我相信”,因为我还没有设法file打印出这样的转义序列,而且我不确定它是否真的在文件名列中这样做。无论如何,-r这里是安全的。
  • -0是这里的关键选项。如果没有它,这个方法就无法可靠地工作。它使fileprint 文件名后面紧接着一个空字符(路径中永远不允许使用的一个字符,因为它通常用于标记 C 程序中的字符串结尾)。这标记了表格中每行两列之间的分隔符。
  • -F ''使file打印内容为空(''为空参数)而不是:。冒号不可靠(它可以出现在文件名中)并且在这里没有任何好处,因为已经打印了一个空字符来指示路径列的结束和描述列的开始。

为了运行,find我使用.的操作替换路径。file --mime-type -r0F '' paths-exec file --mime-type -r0F '' {} +find-exec{} +

5. 消费餐桌

我是这样创建表格的:

find . -exec file --mime-type -r0F '' {} +

如上所述,这会在每个路径后放置一个空字符。如果描述也是以空字符结尾的,那会很方便,但file不会这样做——描述总是以换行符结尾。所以我必须交替读取直到出现空字符,然后假设还有更多文本并读取直到出现换行符。我必须这样做每个文件并在没有剩余内容时停止。

读取每一行

这种组合——读取可能包含换行符直到空字符的文本,然后读取不能包含换行符直到换行符的文本——不是任何常见 Unix 实用程序的正常使用方式。我将采用的方法是将输出通过管道传输find到循环。循环的每次迭代都使用 shell 内置命令两次读取表中的一行read,并使用不同的选项。

读取路径, 我用:

read -rd ''
  • -rread唯一标准选项你应该总是使用它。如果没有它,\n输入中的反斜杠转义符就会被翻译成它们所代表的字符。我们不希望这样。
  • 通常,read它会一直读取直到看到换行符。要忽略换行符并在空字符处停止,我使用-dBash 提供的选项来指定其他字符。对于空字符,请传递空参数''
  • 我已经在使用 Bash 扩展(选项-d),因此当没有将变量名传递给 时,我也可以充分利用 Bash 的默认行为read。它会将读取的所有内容放入--除了特殊变量中的终止符$REPLY。通常会从输入的开头和结尾read去除空格(字符),并且写代码来防止这种情况发生是一种常见的习惯做法。在 Bash 中隐式读取时,这不是必需的。$IFSIFS= read ...$REPLY

阅读说明, 我用:

read -r mimetype
  • MIME 类型中不应出现反斜杠,但最好传递-r到,read除非你 \逃脱翻译。
  • 这次,我明确指定变量名。随便你怎么叫。我选择了mimetype
  • 这次,为了防止前导和尾随空格被删除,缺少了这IFS=一点很重要。我想把它去掉。这会从写入的描述开头删除空格,find以使表格在终端中显示时更易于阅读。

编写循环

只要有另一条路径需要读取,循环就应该继续。read当命令成功读取某些内容时,它返回 true(在 shell 编程中,与几乎所有其他编程语言不同,这是零),当它没有成功读取某些内容时,它返回 false(在 shell 编程中,任何非零值)。所以常见的while read习惯用法在这里很有用。我将--|的输出find(一个或多个(很少)file命令的输出)通过管道 ( ) 传输到while循环中。

find . -exec file --mime-type -r0F '' {} + | while read -rd ''; do
    read -r mimetype
    # Commands using "$REPLY" and "$mimetype" go here.
done

在循环中,我读取了行的其余部分以获取描述(read -r mimetype)。我不费心检查这是否成功。file应该只输出完整的行即使遇到错误. (file发送错误和警告消息至标准误差, 所以他们不会出现在管道中破坏表格。您应该能够依赖这一点。

如果无论如何你都想检查是否read -r mimetype成功,你可以使用if。或者你可以将其包含在while循环条件中:

find . -exec file --mime-type -r0F '' {} + |
while read -rd '' && read -r mimetype; do
    # Commands using "$REPLY" and "$mimetype" go here.
done

您可以看到,为了便于阅读,我还拆分了顶行。(无需\在 处拆分|。)

测试循环

如果您想在继续之前测试循环,则可以将此命令放在注释下(或代替注释)# Commands...

    printf '[%s] [%s]\n\n' "$REPLY" "$mimetype"

循环输出看起来像这样,取决于目录中的内容(为了简洁,我省略了大多数条目):

[.] [inode/directory]

[./stuv] [inode/x-empty]

[./ghi
jkl] [inode/x-empty]

[./fo.o/abc
def   ] [image/jpeg]

[./fo.o/wyz.lep] [application/octet-stream]

[./fo.o/wyz] [image/jpeg]

这只是为了看看循环是否正常工作。[ ]像这样放置表格的条目不会帮助脚本完成它需要做的事情,因为路径可能包含[]和连续的换行符。

6.使用提取的路径和文件类型

在循环的每次迭代中,"$REPLY"包含路径并"$mimetype"包含类型描述。要找出是否"$REPLY"命名 JPEG 文件,请检查是否"$mimetype"恰好为image/jpeg

您可以使用ifand [/ test(或[[)与 来比较字符串=。但我更喜欢case

find -exec file --mime-type -r0F '' {} + | while read -rd ''; do
    read -r mimetype
    case "$mimetype" in image/jpeg)
        # Put commands here that use "$REPLY".
        ;;
    esac
done

如果您只是想以与上述相同的格式显示 JPEG 路径(以帮助测试包含换行符的路径),则整个case...esac语句可以是:

    case "$mimetype" in image/jpeg) printf '[%s]\n\n' "$REPLY";; esac

但目标是lepton在每个 JPEG 文件上运行。为此,请使用:

    case "$mimetype" in image/jpeg) lepton "$REPLY";; esac

7. 综合起来

添加该lepton命令,以及哈希邦线运行它使用 Bash,这是完整的脚本

#!/bin/bash

find . -exec file --mime-type -r0F '' {} + | while read -rd ''; do
    read -r mimetype
    case "$mimetype" in image/jpeg) lepton "$REPLY";; esac
done

lepton报告正在做什么,但不显示文件名。此替代脚本在运行每个路径之前会打印一条消息lepton

#!/bin/bash

find . -exec file --mime-type -r0F '' {} + | while read -rd ''; do
    read -r mimetype
    case "$mimetype" in image/jpeg)
        printf '\nProcessing "%s":\n' "$REPLY" >&2
        lepton "$REPLY"
    esac
done

我已将消息打印到标准误差( >&2),因为这就是lepton发送其自身消息的地方。这样,输出在通过管道传输或重定向时会全部保持在一起。运行该脚本会产生如下输出(但如果您有两个以上的 JPEG,则输出会更多):

Processing "./art.jpg":
lepton v1.0-1.2.1-104-g209463a
6777856 bytes needed to decompress this file
56363 86007
65.53%
2635854 bytes needed to decompress this file
56363 86007
65.53%

Processing "./fo.o/abc
def   ":
lepton v1.0-1.2.1-104-g209463a
6643508 bytes needed to decompress this file
36332 46875
77.51%
2456117 bytes needed to decompress this file
36332 46875
77.51%

每个节中的重复(在运行时lepton不打印文件名也会出现)是因为lepton检查其输出文件是否可以正确解压缩。

您展示的脚本exit 0最后有。您可以根据需要这样做。它会导致脚本始终报告成功。否则,脚本将返回上次命令运行的退出状态——这可能是更好的选择。无论哪种方式,即使 、 或 遇到问题,它也可能报告find成功filelepton如果最后的 lepton命令成功。当然,您可以使用更复杂的错误处理代码来扩展脚本。

8. 也许你也想要路径

如果你想生成路径列表分离lepton自己的输出中,您可以利用 的lepton写入标准错误的行为,通过打印路径标准输出而是。在这种情况下,您可能只想打印路径而不是“正在处理”消息。您可能希望使用空字符而不是换行符来终止路径,因为这样您就可以处理列表而不会在包含换行符的路径上中断。

#!/bin/bash

case "$1" in
    -0) format='%s\0';;
    *)  format='%s\n';;
esac

find . -exec file --mime-type -r0F '' {} + | while read -rd ''; do
    read -r mimetype
    case "$mimetype" in image/jpeg)
        printf "$format" "$REPLY"
        lepton "$REPLY"
    esac
done

运行该脚本时,可以传递标志-0,使其发出空字符而不是换行符。该脚本不执行正确的 Unix 样式选项处理:它仅检查第一的您传递的参数;在同一个参数 ( ) 中重复传递标志-00不起作用;并且不会生成任何与选项相关的错误消息。此限制是为了简洁起见,因为您可能不需要任何更复杂的东西,因为脚本不支持任何非选项参数并且-0是唯一可能的选项。

在我的系统上,我调用了该脚本jpeg-lep3并将其放入~/source,然后运行~/source/jpeg-lep3 -0 > out,它将 just 的输出打印lepton到我的终端。如果您执行了类似操作,则可以使用以下命令测试路径之间是否正确写入了空字符:

xargs -0 printf '[%s]\n\n' < out

答案2

先写代码:

让我们使用 Bash 的特殊 glob 和for循环来完成此操作:

#!/bin/bash
shopt -s globstar dotglob

for f in ./** ; do 
    if file -b -- "$f" | grep -q '^JPEG image data,' ; then 

        # do whatever you want with the JPEG file "$f" in here:
        md5sum -- "$f"

    fi
done

解释:

首先,我们需要通过启用globstar和shell 选项来使 Bash 通配符更加有用。以下是有关 的 SHELL BUILTIN COMMANDS 部分中dotglob的描述:man bashshopt

 dotglob 
    If set, bash includes filenames beginning with a `.' in the results of 
    pathname expansion.
 globstar
    If set, the pattern ** used in a pathname expansion context will match 
    all files and zero or more directories and subdirectories. If the pattern
    is followed by a /, only directories and subdirectories match.

./**然后我们在循环中使用这个新的“递归 glob”for来迭代当前目录及其所有子目录中的所有文件和文件夹。请在 glob 中始终使用绝对路径或以./或开头的显式相对路径../,而不仅仅是**,以防止出现特殊文件名(如 )的问题~

现在我们用该命令测试每个文件(和文件夹)名称file的内容。该-b选项可防止它在内容信息字符串之前再次打印文件名,从而使过滤更加安全。

现在我们知道所有有效的 JPG/JPEG 文件的内容信息都必须以 开头JPEG image data,,这就是我们用 测试 的输出。我们使用选项来抑制任何输出,因为我们只file对的退出代码感兴趣,它指示模式是否匹配。grep-qgrep

如果匹配,则将执行if/块内的代码。我们可以在这里做任何我们想做的事情。当前的 JPEG 文件名在 shell 变量中可用。我们只需确保始终将其放在双引号中,以防止意外评估带有特殊字符(如空格、换行符或符号)的文件名。通常最好将其放在后面,将其与其他参数分开,这会导致大多数命令将其解释为文件名,即使它是类似或的东西,否则将被解释为选项。then$f---v--help


附加问题:

为了科学,是时候炸毁一些代码了!这是你的问题/书中的版本:

for jpeg in `echo "$(file $(find ./ ) 
    | grep JPEG | cut -f 1 -d ':')"`
do
     /path/to/command "$jpeg"
done

首先,请允许我提一下他们编写的程序有多复杂。我们有 4 层嵌套子 shell,使用混合命令替换语法(``$()),这仅仅是因为 的使用不正确/不理想而必需的find

这里find只列出所有文件并打印它们的名称,每行一个。然后将完整输出传递给file检查每个文件。但是等等!每行一个文件名?如果文件名包含换行符怎么办?对,这些会破坏它!

$ ls --escape ne*ne
new\nline
$ file $(find . -name 'ne*ne' )
./new: cannot open `./new' (No such file or directory)
line:  cannot open `line' (No such file or directory)

实际上,即使是简单的空格也会破坏它,因为 也会将它们视为分隔符file。您甚至无法"$(find ./ )"在此处引用 来补救,因为那样会将整个多行输出引用为一个文件名参数。

$ ls simple*
simple spaces.jpg
$ file $(find ./ -name 'simple*')
./simple:   cannot open `./simple' (No such file or directory)
spaces.jpg: cannot open `spaces.jpg' (No such file or directory)

下一步,file使用 扫描输出grep JPEG。你不觉得欺骗这样一个简单的模式有点容易吗,尤其是因为 plain 的输出file总是包含文件名?基本上文件名中带有“JPEG”的所有内容都会触发匹配,无论它包含什么。

$ echo "to be or not to be" > IAmNoJPEG.txt
$ file IAmNoJPEG.txt | grep JPEG
IAmNoJPEG.txt: ASCII text

好的,所以我们有file所有 JPEG 文件(或假装是 JPEG 文件)的输出,现在他们处理所有行以cut从第一列中提取原始文件名,并用冒号分隔......猜猜是什么,让我们在名称中带有冒号的文件上尝试一下:

$ ls colon*
colons:evil.jpeg
$ file colon* | grep JPEG | cut -f 1 -d ':'
colons

所以总而言之,您书中的方法有效,但前提是它检查的所有文件都不包含空格、换行符、冒号和其他特殊字符,并且文件名中不包含字符串“JPEG”。这也有点丑陋,但由于情人眼里出西施,我不会对此喋喋不休。

答案3

您已经find使用命令检查了file它的 mime 类型。

find . -type f -exec file --mime-type -b '{}' +

或者按照如下方式使其完整:

find . -type f -exec sh -c '
    file --mime-type -b "$0" | grep -q "aPATTERN" && printf "$0\n"
' {} \;

或者identifyImageMagic 包中的选项

find -type f -print0 | xargs -0 identify

相关内容