【问题标题】:Double quotes vs asterisk filename expansion in BashBash中的双引号与星号文件名扩展
【发布时间】:2013-03-05 13:08:06
【问题描述】:

在~/temp/a/foo/ 和~/temp/b/foo foo/ 目录中,我有一些名为bar1、bar2、bar bar1、bar bar2 等的文件。

我正在尝试编写一行 Bash,将包含“foo”作为名称最后一部分的目录中的所有这些文件复制到相应“foo”文件夹上方的文件夹中。

只要文件名中没有空格,这很容易,但是以下两个命令在处理foo foo目录时会失败:

for dir in `find . -type d -name '*foo'` ; do cp $dir/* "$(echo $dir|sed 's_foo__g')" ; done

(cp 命令无法将"foo foo" 的最后一个foo 视为同一目录名称的一部分。)

for dir in `find . -type d -name '*foo'` ; do cp "$dir/*" "$(echo $dir|sed 's_foo__g')" ; done

("$dir/*" 未展开。)

尝试将$dir/* 替换为"$(echo $dir/*)" 更不成功。

有没有简单的方法来扩展$dir/* 以便cp 理解?

【问题讨论】:

标签: bash quotes asterisk expansion


【解决方案1】:

for 循环不仅错误——sed 也不是这项工作的正确工具。

while IFS= read -r -d '' dir; do
  cp "$dir" "${dir/foo/}"
done < <(find . -type d -name '*foo' -print0)

在find(和IFS= read -r -d '')上使用-print0 可以确保文件名中包含换行符不会搞砸。

使用&lt; &lt;(...) 构造可确保如果循环内部设置变量、更改目录状态或执行类似操作,这些更改将继续存在(管道的右侧位于 bash 的子 shell 中,因此管道进入 while 循环意味着在该 while 循环内对 shell 状态所做的任何更改都将在退出时被丢弃)。

使用${dir/foo/} 比调用sed 效率更高,因为它在bash 内部进行字符串替换。

【讨论】:

  • 您确定那里需要IFS= 吗?顺便说一句,很棒的帖子!
  • 尽管 janos 建议接受此答案作为解决方案,但事实证明该代码实际上并未移动任何文件。 Bash 4.2.24 抱怨 cp 不能 stat ''。我不是 100% 确定 cp "$dir" "${dir/foo/}" 行是做什么的。它真的会尝试将文件复制到foo 目录上方的现有目录吗?
  • @uvett Dan 指出了这个错误。如果这不能解决问题,请确保您的文件以 #!/bin/bash 开头;此代码不适用于#!/bin/sh。
  • @janos IFS= 防止尾随空格被剥离。 (这也可以通过不指定dir 并允许将读取的内容分配给$REPLY 来避免)。
  • 抱歉,不走运。只要我使用cp -r 并且foo 目录包含foo 之前的字符,我就可以使用这些行移动文件,但在这种情况下,cp -r "$dir" "${dir/foo/}" 行(显然)创建一个基于旧目录名称,减去 foo 部分。对于仅命名为 foo 的目录,该命令将失败。关于如何解决这个问题,我的第一个想法与让我创建此线程的想法完全相同:用cp "$dir/*" 替换查尔斯的cp "$dir" "${dir/foo/}" "${dir/foo}"(没用,因为引号会阻止星号扩展)。
【解决方案2】:

这里的问题不是cp,而是for,因为默认情况下,它会按单词而不是目录名称拆分子shell的输出。

一种懒惰的解决方法是改用while 并像这样逐行处理目录列表:

find . -type d -name '*foo' | while read dir; do cp "$dir"/* "$(echo $dir | sed 's_foo__g')" ; done

这应该可以解决您的空格问题,但这绝不是万无一失的解决方案。

查看 Charles Duffy 的答案以获得更准确的解决方案。

【讨论】:

  • 几乎正确,但您确实应该用 NUL 分隔管道的内容 - 否则,包含换行符的恶意制作的文件名可能会导致任意文件被复制。
  • ...顺便说一句,dir 永远不会包含空格作为for 工作方式的产物,这也是不准确的;分词不是 for 语义的一部分,如果IFS 设置为不包含空格,则字符串拆分输出可以 包含空格。也就是说,由于其他原因(例如,因为 glob 扩展同时发生),依赖字符串拆分是错误的。
  • 谢谢,这似乎适用于我所有现实世界的文件复制问题。在我的简化示例中它实际上失败了,这是一个有趣的细节。 (sed 删除了 foo foo 文件夹名称中的两个 foos)。
  • @CharlesDuffy “几乎是对的”... 离事实有点远 eh :-) 但这正是我添加“绝不是万无一失”位的原因。感谢您的提示,我将改写我写的关于for 的内容。
  • @uvett 我敦促您接受 Charles Duffy 的回答而不是我的回答。它更准确,解释更清楚,并且可以处理所有极端情况。
【解决方案3】:

将~/temp/b/foo foo/ 重命名为不带空格的名称,例如~/temp/b/foo_foo/ 并再次做你想做的事情。之后,如果您确实需要,您可以将其重命名为原始名称,并带有空格。顺便提一句。我自己从不使用包含空格的文件名,只是为了避免像您现在面临的那样复杂。

【讨论】:

  • 我试图简化一个涉及数十个文件夹和数百个名称难看的文件的问题。重命名它们似乎不是最佳选择,尤其是因为我以后必须恢复文件夹名称。
猜你喜欢
  • 2012-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-18
  • 1970-01-01
  • 2015-02-28
  • 2011-11-17
相关资源
最近更新 更多