使用通配符优雅地将数千个 epub 文件转换为 mobi

使用通配符优雅地将数千个 epub 文件转换为 mobi

我正在对 1000 个文件执行以下命令:

ebook-convert <name-of-first-file>.epub <name-of-first-file>.mobi
ebook-convert <name-of-second-file>.epub <name-of-second-file>.mobi

显然,我们可以为这项工作编写一个 bash 脚本,而不是手动对 1000 个文件执行此操作。

我想知道在 Linux 中是否有一种更简单的方法来做这样的事情,一个小命令,看起来像

ebook-convert *.epub *.mobi

您能否以类似的方式使用通配符,以适用于上述场景?

答案1

您不能直接使用通配符来完成此操作,但for可以使用循环来实现:

for epub in ./*.epub; do ebook-convert "${epub}" "${epub%.epub}.mobi"; done

Zsh 支持此循环的更优雅的形式

如果您的文件名不包含空格字符,并且通常可以由 Make 和 shell 安全地处理,则可以使用 GNU Make,而不是使用 shell 脚本;把这个放在一个Makefile

all: $(patsubst %.epub,%.mobi,$(wildcard *.epub))

%.mobi : %.epub
        ebook-convert ./$< ./$@

然后运行make​​,这将确保所有.epub文件都转换为一个.mobi文件。您可以根据需要重复运行此命令来更新文件 - 它只会构建丢失或早于源文件的文件。 (确保该ebook-convert行以制表符开头,而不是空格。)

答案2

带壳zsh

for f (./*.epub) ebook-convert $f $f:r.mobi

扩展$f:r到根名称(没有扩展名的部分),$fcsh/ vim...

或者:

autoload -Uz zmv # best in ~/.zshrc
zmv -P ebook-convert './(*).epub' './$1.mobi`

(由于ebook-convert似乎无法识别--选项分隔符,我们必须使用前缀./并使用-P而不是-p能够处理以 开头的文件名-

zmv主要用于批量重命名,但也用于使用 / 进行批量复制/链接-C-L或者可以扩展到任何形式的更改/转换...通过指定程序使用-p/来执行此操作-P

或者:

autoload -Uz zmv
alias ebc='noglob zmv -WP ebook-convert'
ebc ./*.epub ./*.mobi

使用-W,zmv捕获源模式上的所有通配符,并在替换中使用递增 、 等${1}转换所有通配符。${2}所以zmv -W './*.epub' './*.mobi'与 相同zmv -W './(*).epub' './${1}.mobi'noglob禁用命令参数中的通配符,从而避免引用。

或者:

autoload -Uz zargs # best in ~/.zshrc
zargs -I@ ./*.epub(:r) -- ebook-convert @.epub @.mobi

zargsbeingzshxargs类似命令来批处理参数列表。./*.epub(:r)获取.epub文件的根名称,然后使用-I@运行zargs命令行,并依次ebook-convert将每个根名称替换为每个文件的根名称。@

如果您的ebook-convert命令接受被称为ebook-convert file1.epub file1.mobi file2.epub file2.mobi ...,您还可以执行以下操作:

ebook-convert ./*.epub(e['reply=($REPLY $REPLY:r.mobi)'])

./file1.epub ./file1.mobi ./file2.epub ./file2.mobi由于eval glob 限定符为每个匹配文件运行提供的代码,该 glob 扩展到...,其中设置$reply定义了 glob 应扩展到的参数列表。

或者:

(){epub-convert $^@.{epub,mobi};} ./*.epub(:r)

我们将文件根名列表传递.epub给匿名函数,该函数使用大括号扩展来传递带有.epub.mobi附加参数的参数。

在所有这些中,要将*.epubglob 扩展限制为那些还没有较新的对应.mobi文件的文件,您可以添加该e['[[ ! $REPLY:r.mobi -nt $REPLY ]]']glob 限定符,或将检查添加为循环的一部分:

for epub (./*.epub) {
  mobi=$epub:r.mobi
  [[ $mobi -nt $epub ]] || ebook-convert $epub $mobi
}

答案3

您能否以类似的方式使用通配符,以适用于上述场景?

不像ebook-convert *epub *mobi,因为通配符 - 真正的“shell 通配符” - 的工作原理。但是,您可以从通配符开始。

从概念上讲,Shell 通配符非常简单:找到与该通配符匹配的所有文件,并将该通配符替换为该文件列表,处理空格和其他“特殊”字符,以便调用的操作(通常是一个程序;此处为ebook-convert)获取每个文件文件作为单个单独的参数。

因此,给定一个包含a.epubb.epub、 和 的文件夹file with spaces.epub,shell 将扩展*.epuba.epubb.epub、 和file with spaces.epub作为正在调用的任何内容的 3 个独立参数(此处为ebook-convert)。

给定同一个文件夹,*.mobi不会匹配任何内容,因此ebook-convert将收到一个字面上的参数*.mobi。从ebook-convert的角度来看,它得到了一个包含三个 epub 文件和一个不存在的 mobi 文件的列表;它如何处理参数列表取决于它(据猜测,它要么抱怨参数太多,要么依次尝试将每个 epub 转换为字面名为“*.mobi”的 mobi 文件)。

请注意,对于程序(或内置 shell 或函数或脚本等)如何处理它期望是文件名但包含 glob 的参数,没有全局保证。通常,该参数将被视为字符串文字,并且对*.mobi不存在的反应就像对anything_else.mobi不存在的反应一样,但没有法律规定必须发生这种情况。

对于其他 glob 也会发生同样的情况;例如,?.epub将包括a.epubb.epub,但不包括file with spaces.epub

正如其他人所指出的,你使用 glob 来驱动循环 - for file in *.epub ; do ....请注意,需要引用对“文件”的引用来处理空格:通配符for file in *.epub仅确保它file with spaces.epub是循环本身的单个参数for,但不会扩展到循环体中(即,for file in *.epub ; do ebook-convert $file将发送三个单独的参数对于file with spaces.epub: filewithspaces.epub)。

所有这些也是您经常需要为需要 glob 字符的程序引用参数的原因:这样做可以防止 shell 扩展 glob,并且实际上让被调用的程序看到您编写的参数。例如,find . -type f -name '*.epub'将查找(并打印其名称)此目录和任何子目录中的所有 epub 文件;find . -type f -name *.epub会出错,因为它会看到b.epubandfile with spaces.epub作为参数,但它不知道该怎么做。

答案4

我认为你可以像%.mobi: %.epubGNU make 中接受的答案一样可爱——没有 GNU make 及其文件名限制——用一个小包装器:

from_to(){
    sp=${1%%%*}; ss=${1#*%}; shift
    dp=${1%%%*}; ds=${1#*%}; shift
    for s in "$sp"*"$ss"; do
        d=${s#"$sp"}; d=$dp${d%"$ss"}$ds
        "$@" "$s" "$d" || exit 1
    done
}

你可以用它作为

from_to %.epub %.mobi ebook-convert
from_to dir1/book_%.epub dir2/%.mobi ebook-convert

模拟测试示例:

% touch {1,2,3}.foo
% from_to ./%.foo bar/%.baz echo translate --
translate -- ./1.foo bar/1.baz
translate -- ./2.foo bar/2.baz
translate -- ./3.foo bar/3.baz

不管怎样,你必须使用其他的东西*,因为它在 Unix shell 中有一个非常确定的含义。

相关内容