如何将shell命令应用于嵌套(和转义不良)子目录中的许多文件? [英] How do I apply a shell command to many files in nested (and poorly escaped) subdirectories?

查看:176
本文介绍了如何将shell命令应用于嵌套(和转义不良)子目录中的许多文件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试执行以下操作:

 在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 命令。


  1. bash语句执行。

  2. A - bash 将把它放在 $ 0 中,真的可以把任何你喜欢的东西放在这里。

  3. 您的文件名或文件名(取决于您是否分别使用 {} \; {} + )。文件名在 $ 1 (和 $ 2 $ 3 ,...如果有不止一个,当然)。

第一个 find 命令中的bash 语句将以文件名作为参数运行 somebashstatement / 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 into xargs (even with -0) is rather convoluted as find 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 matches pattern.

The latter runs somecommand with as many arguments as fit on the command line at once for files recursively in /path that match pattern.

Which one to use depends on somecommand. If it can take multiple filename arguments (like rm, grep, etc.) then the latter option is faster (since you run somecommand far less often). If somecommand takes only one argument then you need the former solution. So look at somecommand's man page.

More on find: http://mywiki.wooledge.org/UsingFind

In 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 of bar and tear it apart wherever there are spaces, tabs or newlines (technically, whatever characters are in IFS) 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 above find commands all assume that somecommand is an executable in PATH. If it's a bash statement, you'll need this construct instead (iterates over find'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 string find outputs until it reaches a NULL byte (which is what -print0 uses to separate the filenames). Since NULL 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 use find's -exec to run your bash 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 a bash command with three or more arguments.

  1. The bash statement to execute.
  2. A --. bash will put this in $0, you can put anything you like here, really.
  3. 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 first find command here runs somebashstatement with the filename as argument.

The bash statement in the second find command here runs a for(!) loop that iterates over each positional parameter (that's what the reduced for syntax - for foo; do - does) and runs a somebashstatement with the filename as argument. The difference here between the very first find statement I showed with -exec {} + is that we run only one bash process for lots of filenames but still one somebashstatement for each of those filenames.

All this is also well explained in the UsingFind page linked above.

这篇关于如何将shell命令应用于嵌套(和转义不良)子目录中的许多文件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆