使 BASH 脚本 `for` 处理带有空格的文件名(或解决方法)

Sam*_*hke 12 bash find filenames

虽然我已经使用 BASH 好几年了,但我在 BASH 脚本方面的经验相对有限。

我的代码如下。它应该从当前目录中获取整个目录结构并将其复制到$OUTDIR.

for DIR in `find . -type d -printf "\"%P\"\040"`
do
  echo mkdir -p \"${OUTPATH}${DIR}\"        # Using echo for debug; working script will simply execute mkdir
  echo Created $DIR
done
Run Code Online (Sandbox Code Playgroud)

问题是,这是我的文件结构示例:

$ ls
Expect The Impossible-Stellar Kart
Five Iron Frenzy - Cheeses...
Five Score and Seven Years Ago-Relient K
Hello-After Edmund
I Will Go-Starfield
Learning to Breathe-Switchfoot
MMHMM-Relient K
Run Code Online (Sandbox Code Playgroud)

请注意空格 :-S 并for逐字接受参数,因此我的脚本输出如下所示:

Creating directory structure...
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Learning"
Created Learning
mkdir -p "/myfiles/multimedia/samjmusicmp3test/to"
Created to
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Breathe-Switchfoot"
Created Breathe-Switchfoot
Run Code Online (Sandbox Code Playgroud)

但我需要它从find. 我也试过find在每个文件名周围加上双引号。但这没有帮助。

for DIR in `find . -type d -printf "\"%P\"\040"`
Run Code Online (Sandbox Code Playgroud)

并使用更改后的行输出:

Creating directory structure...
mkdir -p "/myfiles/multimedia/samjmusicmp3test/"""
Created ""
mkdir -p "/myfiles/multimedia/samjmusicmp3test/"Learning"
Created "Learning
mkdir -p "/myfiles/multimedia/samjmusicmp3test/to"
Created to
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Breathe-Switchfoot""
Created Breathe-Switchfoot"
Run Code Online (Sandbox Code Playgroud)

现在,我需要一些可以像这样迭代的方法,因为我还希望运行一个更复杂的命令,该命令涉及gstreamer以下类似结构中的每个文件。我该怎么做?

编辑:我需要一个代码结构,它允许我为每个目录/文件/循环运行多行代码。对不起,如果我不清楚。

解决方案:我最初尝试:

find . -type d | while read DIR
do
  mkdir -p "${OUTPATH}${DIR}"
  echo Created $DIR
done
Run Code Online (Sandbox Code Playgroud)

这在大多数情况下运行良好。然而,我后来发现,由于管道导致 while 循环在子 shell 中运行,循环中设置的任何变量后来都不可用,这使得实现错误计数器变得非常困难。我的最终解决方案(来自SO 上的这个答案):

while read DIR
do
  mkdir -p "${OUTPATH}${DIR}"
  echo Created $DIR
done < <(find . -type d)
Run Code Online (Sandbox Code Playgroud)

这稍后允许我有条件地增加循环中的变量,这些变量将在脚本中稍后保持可用。

Den*_*son 11

您需要将其管道find化为while循环。

find ... | while read -r dir
do
    something with "$dir"
done
Run Code Online (Sandbox Code Playgroud)

此外,-printf在这种情况下您不需要使用。

如果您愿意,您可以通过使用空字节分隔符(这是唯一不能出现在 *nix 文件路径中的字符)来针对名称中包含换行符的文件进行此证明:

find ... -print0 | while read -d '' -r dir
do
    something with "$dir"
done
Run Code Online (Sandbox Code Playgroud)

您还会发现使用$()而不是反引号更通用和更容易。它们可以更轻松地嵌套,并且可以更轻松地进行引用。这个人为的例子将说明以下几点:

echo "$(echo "$(echo "hello")")"
Run Code Online (Sandbox Code Playgroud)

尝试用反引号做到这一点。

  • 这将处理带有空格 (U+0020) 的路径名,但它仍然无法正确处理带有换行符 (U+000A) 的路径名。我更喜欢`find ... -print0 | xargs -0 ...` 因为它使用的分隔符与 POSIX 路径名中唯一不允许的字符完全对应:NUL (U+0000)。 (4认同)
  • 此外,与 `"$dir"` 相比,最好使用 `"${dir}"` - 很容易分辨 ${dir}name 和 ${dirname} 之间的区别,但是 $dirname 可以以任何一种方式解释. (2认同)
  • 完美的!正是我要找的。我从来没有想过你可以通过管道传输到 `while`。@Chris Johnsen:没错,但即使是音乐翻录程序也不倾向于在其文件名中添加换行符。如果他们这样做,我想知道(即:出了点问题)并立即摆脱他们...... (2认同)

Jam*_*ley 8

请参阅我几天前写的这个答案,以获取处理带空格的文件名的脚本示例。

有一种稍微复杂(但更简洁)的方法来实现你想要做的事情:

find . -type d -print0 | xargs -0 -I {} mkdir -p ../theredir/{}
Run Code Online (Sandbox Code Playgroud)

-print0告诉 find 用 null 分隔参数;-0 到 xargs 告诉它期待由空值分隔的参数。这意味着它可以很好地处理空格。

-I {}告诉 xargs{}用文件名替换字符串。这也意味着每个命令行只应使用一个文件名(xargs 通常会填充尽可能多的内容)

其余的应该是显而易见的。


Dar*_*all 5

您遇到的问题是 for 语句将 find 作为单独的参数进行响应。空格分隔符。您需要使用 bash 的 IFS 变量来不分割空间。

这是一个解释如何执行此操作的链接

IFS 内部变量

解决此问题的一种方法是更改​​ Bash 的内部 IFS(内部字段分隔符)变量,以便它通过除默认空格(空格、制表符、换行符)以外的其他内容(在本例中为逗号)来拆分字段。

#!/bin/bash
IFS=$';'

for I in `find -type d -printf \"%P\"\;`
do
   echo "== $I =="
done
Run Code Online (Sandbox Code Playgroud)

将您的 find 设置为在 %P 之后输出您的字段分隔符并适当地设置您的 IFS。我选择了分号,因为它极不可能在您的文件名中找到。

另一种选择是直接从 find 调用 mkdir-exec是否可以完全跳过 for 循环。那是如果您不需要进行任何额外的解析。

  • 你可以在 POSIX 上选择 `/`,在 DOS 文件系统上选择 `:`。您可以为 IFS 选择不同文件系统的非法字符。任何更复杂的事情,你最好使用 perl。 (3认同)
  • 使用 / 的问题在于它是目录分隔符,而 `find` 返回的文件名的路径包括斜杠。尝试将脚本中的分号更改为斜杠,回显将在单独的行中打印目录和文件名。 (2认同)