6

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

for file in `find . *.foo`
do
somecommand $file
done

但是该命令不起作用,因为 $file 非常奇怪。因为我的目录树有蹩脚的文件名(包括空格),我需要转义find命令。但似乎没有一个明显的转义符起作用: -ls给我以空格分隔的文件名片段 -fprint并没有更好的效果。

我也试过: for file in "找到 . *.foo -ls"; do echo $file; done - but that gives all of the responses from find in one long line.

有什么提示吗?我很高兴有任何解决方法,但很沮丧我无法弄清楚这一点。

谢谢,亚历克斯

(嗨,马特!)

4

6 回答 6

11

您有很多答案可以很好地解释如何做到这一点;但为了完整起见,我将重复并添加:

xargs仅对交互式使用有用(当您知道所有文件名都是普通的 - 没有空格或引号时)或与-0选项一起使用时。否则,它会破坏一切。

find是一个非常有用的工具;put 使用它来将文件名通过管道传输到xargs(即使使用-0)相当复杂,因为find它可以自己完成,-exec command {} \;或者-exec command {} +取决于你想要的:

find /path -name 'pattern' -exec somecommand {} \;
find /path -name 'pattern' -exec somecommand {} +

前者在match 中递归地为每个文件somecommand运行一个参数/pathpattern

后者在该 match中以递归方式一次在命令行上somecommand使用尽可能多的参数运行文件。/pathpattern

使用哪一个取决于somecommand. 如果它可以采用多个文件名参数(如rm,grep等),那么后一个选项会更快(因为你运行somecommand的频率要低得多)。如果somecommand只接受一个参数,那么您需要前一种解决方案。所以看看somecommand's man page。

更多关于findhttp://mywiki.wooledge.org/UsingFind

In bash,for是一个迭代参数的语句。如果你做这样的事情:

for foo in "$bar"

你给了for 一个参数来迭代(注意引号!)。如果你做这样的事情:

for foo in $bar

您要求在有空格、制表符或换行符的地方(从技术上讲,任何字符在 中)都bash将其内容bar撕开,IFS并将该操作的各个部分用作 for 的参数。 那不是文件名。假设在一堆文件名中存在空格的地方将包含文件名的长字符串撕裂的结果是错误的。正如你刚刚注意到的那样。

答案是:不要使用for,这显然是错误的工具。上述find命令都假定它somecommandPATH. 如果它是一个bash语句,则您将需要此构造(迭代find' 的输出,就像您尝试过的那样,但安全):

while read -r -d ''; do
    somebashstatement "$REPLY"
done < <(find /path -name 'pattern' -print0)

这使用一个while-read循环读取部分字符串find输出,直到它到达一个NULL字节(这是-print0用来分隔文件名的)。由于NULL字节不能是文件名的一部分(与空格、制表符和换行符不同),这是一个安全的操作。

如果您不需要somebashstatement成为脚本的一部分(例如,它不会通过保留计数器或设置变量等来更改脚本环境),那么您仍然可以使用find's-exec来运行您的bash语句:

find /path -name 'pattern' -exec bash -c 'somebashstatement "$1"' -- {} \;
find /path -name 'pattern' -exec bash -c 'for file; do somebashstatement "$file"; done' -- {} +

在这里,-exec执行bash带有三个或更多参数的命令。

  1. 要执行的 bash 语句。
  2. 一个--bash会把这个放进去$0,你可以把任何你喜欢的东西放在这里,真的。
  3. 您的文件名或文件名(取决于您是否使用{} \;{} +分别)。文件名以$1(当然,如果有多个文件名,还有$2, , ...)结束。$3

此处第一个命令中的bash语句以文件名作为参数运行。findsomebashstatement

这里第二个命令中的bash语句运行一个( ! ) 循环,该循环遍历每个位置参数(这就是简化的语法 - - 所做的)并以文件名作为参数运行 a 。我展示的第一个语句之间的区别在于,我们只为大量文件名运行一个进程,但仍然为每个文件名运行一个进程。findforforfor foo; dosomebashstatementfind-exec {} +bashsomebashstatement

UsingFind所有这些在上面链接的页面中也得到了很好的解释。

于 2009-04-16T06:17:23.247 回答
9

与其依靠 shell 来完成这项工作,不如依靠 find 来完成:

find . -name "*.foo" -exec somecommand "{}" \;

然后文件名将被正确转义,并且永远不会被 shell 解释。

于 2009-04-15T21:34:58.323 回答
2
find . -name '*.foo' -print0 | xargs -0 -n 1 somecommand

但是,如果您需要在每个项目上运行多个 shell 命令,它确实会变得混乱。

于 2009-04-15T21:35:57.480 回答
1

xargs是你的朋友。您还需要使用它来研究 -0(零)选项。 find(with -print0) 将有助于生成列表。维基百科页面有一些很好的例子。

另一个有用的使用理由xargs是,如果你有很多文件(几十个或更多),xargs 会将它们拆分为单独的调用,然后调用任何 xargs 来运行(在第一个维基百科示例中,rm

于 2009-04-15T21:35:40.350 回答
1
find . -name '*.foo' -print0 | xargs -0 sh -c 'for F in "${@}"; do ...; done' "${0}"
于 2009-04-16T01:49:14.110 回答
0

I had to do something similar some time ago, renaming files to allow them to live in Win32 environments:

#!/bin/bash
IFS=$'\n'
function RecurseDirs
{
for f in "$@"
do
  newf=echo "${f}" | sed -e 's/[\\/:\*\?#"\|<>]/_/g'
  if [ ${newf} != ${f} ]; then
    echo "${f}" "${newf}"
    mv "${f}" "${newf}"
    f="${newf}"
  fi
  if [[ -d "${f}" ]]; then
    cd "${f}"
    RecurseDirs $(ls -1 ".")
  fi
done
cd ..
}
RecurseDirs .

This is probably a little simplistic, doesn't avoid name collisions, and I'm sure it could be done better -- but this does remove the need to use basename on the find results (in my case) before performing my sed replacement.

I might ask, what are you doing to the found files, exactly?

于 2009-04-16T21:24:55.950 回答