【问题标题】:Why is this loop breaking after the first iteration?为什么第一次迭代后这个循环会中断?
【发布时间】:2017-11-26 16:12:00
【问题描述】:

尝试遍历目录中的所有文件,检查它们是否存在字符串,如果不存在则添加它。这就是我所拥有的:

#!/bin/bash
FILES=*
for f in $FILES
do
    echo "Processing $f file..."
    if grep -Fxq '<?xml version="1.0" encoding="UTF-8"?>' $f
    then
        continue
    else
        echo '<?xml version="1.0" encoding="UTF-8"?>' | cat - $f > temp && mv temp $f
    fi
done

...但脚本在第一个循环后停止。任何想法为什么?

【问题讨论】:

  • 这取决于你在哪个目录启动脚本?有文件吗?更多?
  • 不要使用FILES;它受参数扩展分词的影响,如果任何文件名包含空格,则会导致问题。只需使用for f in *
  • 字符串应该添加到哪里?您的问题有点简洁,因为它没有正确解释要求
  • bash -x yourscript
  • 1) 直接在 for 循环中使用 glob。 2) 学习在 Bash 扩展周围使用"quotes",否则你会很痛苦。因此,无论您在哪里拥有 $f,您都可以使用 "$f",否则任何名称中带有空格的文件在 Bash 中看起来就像两个文件。

标签: bash loops


【解决方案1】:

一个更简单的解决方案是使用sed 工具的就地编辑选项-i,如下所示

sed -i  '1{/^<?xml version="1.0" encoding="UTF-8"?>/!{
s/^/<?xml version="1.0" encoding="UTF-8"?>\n/}}' /path/to/files/*

我们在上面做什么

  • inplace 选项-ised 对写入文件的文件进行任何更改。
  • 1{} 我们只处理文件的第一行
  • /^&lt;?xml version="1.0" encoding="UTF-8"?&gt;/! 部分检查字符串是否NOT(注意末尾的 !)出现在行首。
  • 如果上述条件不成立,我们使用

    将行的开头 (^) 替换为 &lt;?xml version="1.0" encoding="UTF-8"?&gt;\n
         s/^/<?xml version="1.0" encoding="UTF-8"?>\n/
    
  • 其余的是以正确的顺序关闭大括号:)

也就是说,在您的原始脚本中,我看到了像 FILES 这样的变量。不鼓励使用大写变量作为用户变量,因为它们被保留为环境变量并可能导致冲突。所以请改用files

再做一次

file=*

具有[ word splitting ] 的含义,如果您有包含空格甚至换行符的非标准文件,则会产生不希望的结果。你能做的是

files=( * ) # This put the files in an array
for file in "${files[@]}" # Double quoting the array prevents word splitting
do
 # Do something with "$file" but why bother when you've a one-liner with sed? ;-)
done

注意:对于sed手动访问[ here ]

【讨论】:

  • @dawg 我忽略了这一点.. 非常正确.. 他应该做的是files= ( * ),然后从数组中读取值,例如for file in "${files[@]}"
  • 最好使用for f in in *,因为( * ) 仍然不允许包含\n 的文件名。罕见,但可能。
  • @dawg,嗯? files=( * ) 将每个 glob 结果存储为单独的列表条目。换行符工作正常。也许你在想IFS=$'\n'; files=( $(...) )那个确实有问题。
  • 早上喝咖啡之前。是的——查尔斯是正确的。忽略我对( * )的评论
  • @sjsam @dawg 我认为FILES=* 的分词不是问题,请查看我的答案。
【解决方案2】:

我想澄清一些关于我在 cmets 中看到的分词和文件名扩展的问题。

使用变量赋值时,引用Bash Reference Manual,只进行以下扩展:波浪号扩展、参数扩展、命令替换、算术扩展。这意味着您的变量$files 中确实只有一个星号,因为没有发生文件名扩展。因此,此时您无需担心换行符、空格等,因为您的变量中没有实际文件。您可以通过declare -p files 看到这一点。

这就是你在赋值给变量时不必引用的原因。

var=$othervariable

等同于:

var="$othervariable"

现在,当您在 for 循环 for f in $files 中使用变量 $files 时(请注意,您不能在此处引用 $files,因为不会发生文件名扩展)该变量会被扩展并且 接受字分裂。但实际值是JUST,星号和分词不会对结果产生任何影响!再次引用manual

分词后,除非设置了 -f 选项(请参阅 The Set 内置),Bash 会扫描每个单词中的字符“*”、“?”和“[”。 如果出现这些字符之一,则该词被视为 模式,并替换为按字母顺序排列的文件名列表 匹配模式(请参阅模式匹配)。

这意味着文件名扩展是在变量扩展和分词之后完成的。所以通过文件名扩展扩展的文件不会被IFS分割!因此,以下代码可以正常工作:

#!/usr/bin/env bash

files=*
for f in $files; do
   echo "<<${f}>>"
done

并正确输出:

<<file    with many     spaces>>
<<filewith* weird   characters[abc]>>
<<normalfile>>

一个更短的版本显然是使用for f in * 而不是变量$files。您肯定还想在循环中引用 $f 的任何用法,因为该扩展确实经历了分词。


话虽如此,您的循环应该可以正常运行。

【讨论】: