我正在对 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
如果您的文件名不包含空格字符,并且通常可以由 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
到根名称(没有扩展名的部分),$f
如csh
/ 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
zargs
beingzsh
的xargs
类似命令来批处理参数列表。./*.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
由于e
val glob 限定符为每个匹配文件运行提供的代码,该 glob 扩展到...,其中设置$reply
定义了 glob 应扩展到的参数列表。
或者:
(){epub-convert $^@.{epub,mobi};} ./*.epub(:r)
我们将文件根名列表传递.epub
给匿名函数,该函数使用大括号扩展来传递带有.epub
和.mobi
附加参数的参数。
在所有这些中,要将*.epub
glob 扩展限制为那些还没有较新的对应.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.epub
、b.epub
、 和 的文件夹file with spaces.epub
,shell 将扩展*.epub
为a.epub
、b.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.epub
和b.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
: file
、with
和spaces.epub
)。
所有这些也是您经常需要为需要 glob 字符的程序引用参数的原因:这样做可以防止 shell 扩展 glob,并且实际上让被调用的程序看到您编写的参数。例如,find . -type f -name '*.epub'
将查找(并打印其名称)此目录和任何子目录中的所有 epub 文件;find . -type f -name *.epub
会出错,因为它会看到b.epub
andfile with spaces.epub
作为参数,但它不知道该怎么做。
答案4
我认为你可以像%.mobi: %.epub
GNU 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 中有一个非常确定的含义。