如何将shell命令应用于嵌套(和转义不良)子目录中的许多文件? [英] How do I apply a shell command to many files in nested (and poorly escaped) subdirectories?
问题描述
我正在尝试执行以下操作:
在find中的文件。 * .foo`
do
somecommand $ file
done
但是命令不起作用,因为$ file非常奇怪。因为我的目录树有文件名(包括空格),我需要转义 find
命令。但是没有一个明显的转义似乎有效:
-ls
给出了空格分隔的文件名片段
-fprint
不会做得更好。
我也尝试过: for
find 。* .foo -ls ;做echo $ file;完成
- 但是它能够在一行中找到所有的响应。
任何提示?我很高兴有任何解决方法,但是我感到沮丧,我无法理解这一点。
谢谢,
Alex
(Hi Matt!)
你有很多答案可以很好地解释如何做;但是为了完成,我将重复并添加:
xargs
只对交互式有用使用(当你知道你的所有文件名是简单的 - 没有空格或引号),或者当与 -0
选项一起使用时。否则会破坏一切。
find
是一个非常有用的工具;把它用于管理文件名到 xargs
(甚至与 -0
)相当卷曲为找到
可以使用 -exec command {} \;
或 -exec command {} + / code>取决于你想要的:
find / path -name'pattern'-exec somecommand {} \\ \\;
find / path -name'pattern'-exec somecommand {} +
somecommand
与每个文件的一个参数递归递送在 / path
中匹配 pattern
。
后者运行 somecommand
与一样多的参数在 / path
中与 pattern
匹配的文件一次递送到命令行 p>
使用哪一个取决于 somecommand
。如果它可以采用多个文件名参数(如 rm
, grep
等),那么后一个选项更快你经常运行 somecommand
)。如果 somecommand
只需要一个参数,则需要前一个解决方案。所以看看 somecommand
的手册页。
更多关于 find
: http://mywiki.wooledge.org/UsingFind
在 bash
中, 是一个循环使用参数的语句。如果你这样做:
在$ bar中的foo
/ pre>
您正在为 一个参数提供
循环(注意报价!)。如果你这样做:
for $ bar
你要求
bash
取内容bar
并将其分开,无论有空格,制表符或换行符(技术上,在IFS
中的任何字符),并使用该操作的部分作为参数。 这不是文件名。假设在一堆文件名中,包含文件名的文件名分开的空格的结果是错误的。正如你刚刚注意到的那样。
答案是:不要使用的
,这显然是错误的工具。以上
查找
命令都假定somecommand
是PATH
。如果它是一个bash
语句,那么你需要这个结构(迭代find
的输出,就像你试过的,但安全地):while -r -d''; do
somebashstatement$ REPLY
done< <(find / path-name'pattern'-print0)
这使用
while-read
循环,读取部分字符串find
输出,直到它达到NULL
字节(这是-print0
用于分隔文件名)。由于NULL
字节不能是文件名的一部分(与空格,制表符和换行符不同)这是一个安全的操作。
如果您不需要
somebashstatement
作为脚本的一部分(例如,它不会通过保留计数器或设置变量或其他一些变量来更改脚本环境)然后你仍然可以使用find
的-exec
运行你的bash
语句:find / path -name'pattern'-exec bash -c'somebashstatement$ 1' - - {} \;
find / path -name'pattern'-exec bash -c'用于文件;做somebashstatement$ file;完成' - {} +
这里,
-exec
执行具有三个或更多参数的bash
命令。
- bash语句执行。
- A
-
。bash
将把它放在$ 0
中,真的可以把任何你喜欢的东西放在这里。
- 您的文件名或文件名(取决于您是否分别使用
{} \;
或{} +
)。文件名在$ 1
(和$ 2
,$ 3
,...如果有不止一个,当然)。
第一个
语句将以文件名作为参数运行find
命令中的bashsomebashstatement
/ p>
第二个
find
命令中的bash
语句运行(!)循环中的循环遍历每个位置参数(这就是
>语法 -for foo; do
- 确实)并运行一个somebashstatement
,文件名作为参数。我用-exec {} +
显示的第一个查找
语句之间的区别是,我们只运行一个bash
处理许多文件名,但仍然有一个somebashstatement
,用于这些文件名的 。 p>
所有这一切也在上面链接的
UsingFind
页面中得到很好的解释。I'm trying to do something like the following:
for file in `find . *.foo` do somecommand $file done
But the command isn't working because $file is very odd. Because my directory tree has crappy file names (including spaces), I need to escape the
find
command. But none of the obvious escapes seem to work:-ls
gives me the space-delimited filename fragments-fprint
doesn't do any better.I also tried:
for file in "
find . *.foo -ls"; do echo $file; done - but that gives all of the responses from find in one long line.
Any hints? I'm happy for any workaround, but am frustrated that I can't figure this out.
Thanks, Alex
(Hi Matt!)
解决方案You have plenty of answers that explain well how to do it; but for the sake of completion I'll repeat and add to it:
xargs
is only ever useful for interactive use (when you know all your filenames are plain - no spaces or quotes) or when used with the-0
option. Otherwise, it'll break everything.
find
is a very useful tool; put using it to pipe filenames intoxargs
(even with-0
) is rather convoluted asfind
can do it all itself with either-exec command {} \;
or-exec command {} +
depending on what you want:find /path -name 'pattern' -exec somecommand {} \; find /path -name 'pattern' -exec somecommand {} +
The former runs
somecommand
with one argument for each file recursively in/path
that matchespattern
.The latter runs
somecommand
with as many arguments as fit on the command line at once for files recursively in/path
that matchpattern
.Which one to use depends on
somecommand
. If it can take multiple filename arguments (likerm
,grep
, etc.) then the latter option is faster (since you runsomecommand
far less often). Ifsomecommand
takes only one argument then you need the former solution. So look atsomecommand
's man page.More on
find
: http://mywiki.wooledge.org/UsingFindIn
bash
,for
is a statement that iterates over arguments. If you do something like this:for foo in "$bar"
you're giving
for
one argument to iterate over (note the quotes!). If you do something like this:for foo in $bar
you're asking
bash
to take the contents ofbar
and tear it apart wherever there are spaces, tabs or newlines (technically, whatever characters are inIFS
) and use the pieces of that operation as arguments to for. That is NOT filenames. Assuming that the result of a tearing long string that contains filenames apart wherever there is whitespace yields in a pile of filenames is just wrong. As you have just noticed.The answer is: Don't use
for
, it's obviously the wrong tool. The abovefind
commands all assume thatsomecommand
is an executable inPATH
. If it's abash
statement, you'll need this construct instead (iterates overfind
's output, like you tried, but safely):while read -r -d ''; do somebashstatement "$REPLY" done < <(find /path -name 'pattern' -print0)
This uses a
while-read
loop that reads parts of the stringfind
outputs until it reaches aNULL
byte (which is what-print0
uses to separate the filenames). SinceNULL
bytes can't be part of filenames (unlike spaces, tabs and newlines) this is a safe operation.If you don't need
somebashstatement
to be part of your script (eg. it doesn't change the script environment by keeping a counter or setting a variable or some such) then you can still usefind
's-exec
to run yourbash
statement:find /path -name 'pattern' -exec bash -c 'somebashstatement "$1"' -- {} \; find /path -name 'pattern' -exec bash -c 'for file; do somebashstatement "$file"; done' -- {} +
Here, the
-exec
executes abash
command with three or more arguments.
- The bash statement to execute.
- A
--
.bash
will put this in$0
, you can put anything you like here, really.- Your filename or filenames (depending on whether you used
{} \;
or{} +
respectively). The filename(s) end(s) up in$1
(and$2
,$3
, ... if there's more than one, of course).The
bash
statement in the firstfind
command here runssomebashstatement
with the filename as argument.The
bash
statement in the secondfind
command here runs afor
(!) loop that iterates over each positional parameter (that's what the reducedfor
syntax -for foo; do
- does) and runs asomebashstatement
with the filename as argument. The difference here between the very firstfind
statement I showed with-exec {} +
is that we run only onebash
process for lots of filenames but still onesomebashstatement
for each of those filenames.All this is also well explained in the
UsingFind
page linked above.这篇关于如何将shell命令应用于嵌套(和转义不良)子目录中的许多文件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!