我编写了一些脚本foo
,它接受文件路径,读取文件,对其应用一些更改,并将更改后的文件输出到标准输出:
foo src/file.foo > dest/file.changed.foo # works fine
cat src/file.foo | foo > dest/file.changed.foo # also works fine
现在,我想将此命令应用于目录中的多个文件,并对每个文件执行以下操作:
src/file.foo --> foo --> dest/file.changed.foo
find src -name '*.foo' \
-exec bash -c 'for x; do dest=${x/src/dest}; foo ${x} > ${dest/\.foo$/.changed.foo}; done' _ {} +
上面的方法可行,但问题是对于日常使用来说太复杂了。难道就没有更简单的方法吗?我想知道是否是foo
这样做的方式让事情变得复杂了。
答案1
如果您使用递归通配符,那么打字和摆弄就会少得多。在bash中,输入shopt -s globstar
在你的.bashrc
。在 ksh93 中,递归通配符需要set -o globstar
;在 zsh 中,它开箱即用。请注意,在 bash 中,递归通配符也会在目录的符号链接下递归。
要节省字符串操作,请首先更改到源树的顶部(或目标树的顶部)。
cd foo
for x in **/*.foo; do foo "$x" >"../dest/${x%.*}.changed.foo"; done
如果您知道文件名不包含空格或通配符,则可以省略双引号。
在 zsh 中,双引号是不必要的,即使不更改当前目录,您也可以节省更多的输入。
for x in src/**/*.foo; do foo $x >../dest/${${x#*/}%.*}.changed.foo; done
for x in src/**/*.foo; do foo $x >../dest/${x#*/}:r.changed.foo; done
for x (src/**/*.foo) foo $x >../dest/${x#*/}:r.changed.foo
如果你经常这样做,你应该定义一个构建规则,例如在 GNUmakefile 中(使用一个制表符,其中我使用了 8 个空格):
source_files = $(shell find src -name '*.foo')
destination_files = $(patsubst src/,dest/,$($(source_files)%.foo=.changed.foo))
default: all-foo
all-foo: $(destination_files)
dest/%.changed.foo: src/%.foo
foo $< >$@
答案2
假设您的文件名没有嵌入换行符:
find src -name '*.foo' -type f | \
while IFS= read -r sf; do
df=dest/${sf##./src/} && \
mkdir -p "$(dirname "$df")" && \
foo "$sf" >"${df%%.foo}.changed.foo"
done
答案3
假设您没有源文件树(例如 no src/dir1/a.foo
;它们都直接在src
目录中),那么您不需要find
包装器,只需直接在 shell 中执行即可:
for x in src/*.foo
do
dest="${x/src/dest}"
foo "$x" > "${dest/%.foo/.changed.foo}"
done
这里的复杂之处在于您想要更改文件名,并且这不是foo
.
foo
如果不需要在当前目录中运行,我们可以让它稍微容易一些
cd src
for x in *.foo
do
foo "$x" > "../dest/${x/%.foo/.changed.foo}"
done
如果您不想更改扩展名,那么这就是最简单的循环
cd src
for x in *.foo
do
foo "$x" > "../dest/$x"
done
通过策略性地使用角色,所有这些循环都可以变成单行;
。例如第一个循环变成
for x in src/*.foo; do dest="${x/src/dest}" ; foo "$x" > "${dest/%.foo/.changed.foo}" ; done