如何在“find (...) -exec (...) {} \;”bash命令中替换{}中的字符串?

13
我该如何在 bash 的“find”命令中替换找到的字符串 "{}"?
例如,我想用问号“?”将下面的 "in" 替换为 "out":
find . -name "*.in" -exec python somescript.py {} ? \;
即对所有 "*.in" 文件执行以下操作: python somescript.py somefile.in somefile.out

1
如果有意义的话,您也可以修改somescript.py。 - perreal
6个回答

21

find 没有替换功能。您需要调用 shell。

find . -name "*.in" -exec sh -c 'python somescript.py "$0" "${0%.in}.out"' {} \;

$0 是文件名,${0%.in} 去掉后缀 .in

或者在 bash(但不是纯 sh)中运行 shopt -s globstar 以启用递归目录扩展(如果存在风险没有匹配的 .in 文件,则使用 shopt -s nullglob),并使用 for 循环而不是 find

for x in **/*.in; do
  python somescript.py "$x" "${x%.in}.out"
done

也许还需要使用 shopt -s nullglob 或者 [[ -e $x ]]||continue - Nahuel Fouilleul
我该如何使用替换来编写它?我尝试了 "${0/in/out}" 但是出现了“Bad substitution”错误。 - jul
@jul 在 sh 中没有替代方法。在 bash 中,这将无错误地运行,但通常不会产生正确的结果(例如,它将把 large.inputs/foo.in 转换成 large.outputs/foo.in)。 - Gilles 'SO- stop being evil'
好的,这在 bash 中可以工作。正如您所评论的,我只需要注意替换模式出现多次的情况。 - jul

5

使用花括号扩展

find . -name "*.in" -exec bash -c 'python script.py "${0%.*}"{.in,.out}' {} \;

使用Shell参数扩展

find . -name "*.in" -exec bash -c 'python script.py "${0} ${0/.in/.out}"' {} \;

结果:

python script.py somefile.in somefile.out

1

对我来说,所有内置的工具都有点麻烦(我是否正确使用了shell引用?它是否会做正确的事情?我能轻松预览更改吗?)。

几年前,我编写了 rpt,它将接受其命令行参数,打开文本编辑器(让您编辑文件名),然后运行一个命令(默认为mv)在旧文件名和新文件名上。

最近,我又进一步发展了这个工具,这就是 fea(对于每个参数):

#!/usr/bin/env python3
# fea: For Each Argument
# https://thp.io/2019/rpt-and-fea.html

import sys, shlex, re

_, cmd, *args = sys.argv

print('\n'.join(re.sub(r'[{](.)([^}]*?)(\1)([^}]*?)(\1)[}]',
    lambda m: shlex.quote(re.sub(m.group(2), m.group(4), arg)),
    cmd).replace('{}', shlex.quote(arg)) for arg in args))

以下替换发生:
{} -> insert arg
{/regex/replacement/} -> run re.sub(regex, replacement) on the arg,
                         you can pick any character for "/", as long
                         as it appears at the start, middle and end
                         (to separate the regex from the replacement)

一些用例:
# Create backup files
fea "cp {} {}.bak" *.py

# Encode WAV files with oggenc
fea "oggenc {} -o {/wav$/ogg/}" *.wav

# Decode MP3 files with mpg123
fea "mpg123 -w {/mp3$/wav/} {}" *.mp3

# Render markdown documents to HTML
fea "markdown {} > {/md$/html/}" *.md

# Fancy replacement
fea "markdown {} > {#input/(.*).md#output/\1.html#}" input/*.md

# As mentioned above, note that you need to pipe the
# fea output into a shell to execute the command
fea "cp {} {}.bak" *.py | sh -e -x

您的用例可以这样描述:

find . -name '*.in' -print0 | xargs -0 fea "python somescript.py {} {/in$/out/}"

或者,如果文件只存在于当前文件夹中:
fea "python somescript.py {} {/in$/out/}" *.in

如果你正在使用 zsh 并且需要 递归通配符

fea "python somescript.py {} {/in$/out/}" **/*.in

如果你对显示的命令感到满意,只需将其输出导入到sh -e -x(或bash或其他)中执行。
虽然使用shell(或任何参数/变量扩展)可以创建for循环,但是对于我来说,shell引用和转义非常难以正确处理,fea工具确保它也适用于奇怪的名称。
touch 'some$weird "filename.py'
touch 'and !! more.py'
touch 'why oh why?.py'
touch "this is ridiculous'.py"
fea "cp {} {}.bak" *.py | sh -e -x

请参阅rpt and fea博客文章以获取详细信息。

1

或者,只需修改somescript.py来处理创建输出文件名的任务(在Python中比在shell中更容易,因为没有可能以某种方式解释的特殊字符):

find . -name '*.in' -exec python somescript.py {} .out \;

随着脚本的开始:

import sys, os
inputfilename = sys.argv[1]
outputfilename = os.path.splitext(inputfilename)[0] + sys.argv[2]

# ...

当然,你可以硬编码 .out 并且省略该参数。

如果由于某些原因无法修改 somescript.py,则可以轻松创建一个包装器脚本来处理正确调用 somescript.py 的问题。


0

使用bash -c命令与find命令:

find -name "*.in" -exec bash -c 'python somescript.py "$1" "$(dirname "$1")/$(basename "$1" .in).out"' _ {} \;

找到的文件名为{},作为$1参数传递给bash

basename命令删除扩展名,dirname保留路径名。


这里 basename 不起作用:它会删除目录部分,但目录部分很重要。 - Gilles 'SO- stop being evil'
1
返回已翻译的文本: 或者 ${1/%.in/.out} 或更短的 ${1%.in}.out - Nahuel Fouilleul
@NahuelFouilleul 不,那会将例如 large.inputs/foo.in 改为 large.outputs/foo.in。要去掉后缀,请使用 ${1%.in} - Gilles 'SO- stop being evil'
@Gilles 感谢您的反馈,但是在“/”之后加上“%”可以确保模式位于末尾,但后来我意识到替换是不必要的。 - Nahuel Fouilleul

0

一种易于阅读的方式是这样的:

find -name "*.in" | while read file; do
 python somescript.py $file ${file/.in/.out}
done

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接