【问题标题】:Preserving leading white space while reading>>writing a file line by line in bash在读取>>在bash中逐行写入文件时保留前导空格
【发布时间】:2009-10-30 04:49:55
【问题描述】:

我正在尝试遍历文本文件目录并将它们合并到一个文档中。这很好用,但是文本文件包含代码 sn-ps,并且我的所有格式都向左折叠。一行上的所有前导空格都被删除。

#!/bin/sh
OUTPUT="../best_practices.textile"
FILES="../best-practices/*.textile"
for f in "$FILES"
do
  echo "Processing $f file..."
  echo "">$OUTPUT

  cat $f | while read line; do 
      echo "$line">>$OUTPUT
  done
  echo >>$OUTPUT
  echo >>$OUTPUT
done

无可否认,我是一个 bash 菜鸟,但是在搜索了高低之后,我找不到合适的解决方案。显然 BASH 讨厌一般的领先空白。

【问题讨论】:

    标签: bash parsing text-files cat


    【解决方案1】:

    正如其他人所指出的,使用 cat 或 awk 代替 read-echo 循环是一种更好的方法 - 避免了空白修剪问题(以及您没有偶然发现的其他几个问题),运行速度更快,至少使用 cat 是更简洁的代码。尽管如此,我还是想尝试一下让 read-echo 循环正常工作。

    首先,空格修剪问题:read 命令自动修剪前导和尾随空格;这可以通过将 IFS 变量设置为空白来更改其空白定义来解决。此外, read 假设行尾的反斜杠表示下一行是续行,应该与这一行拼接在一起;要解决此问题,请使用其 -r (原始)标志。这里的第三个问题是 echo 的许多实现解释了字符串中的转义序列(例如,它们可能将 \n 变成实际的换行符);要解决此问题,请改用 printf。最后,就像一般的脚本卫生规则一样,当你实际上不需要时,你不应该使用 cat ;改用输入重定向。通过这些更改,内部循环如下所示:

    while IFS='' read -r line; do 
      printf "%s\n" "$line">>$OUTPUT
    done <$f
    

    ...周围的脚本还有一些其他问题:尝试将 FILES 定义为可用 .textile 文件列表的行有引号,这意味着它永远不会扩展为实际的文件列表.最好的方法是使用数组:

    FILES=(../best-practices/*.textile)
    ...
    for f in "${FILES[@]}"
    

    (并且所有出现的 $f 都应该用双引号引起来实际上可以安全地离开。)

    最后,循环文件顶部附近有一个echo ""&gt;$OUTPUT,它将在每次通过时都会擦除输出文件(即最后,它只包含最后一个 .textile 文件);这需要移到循环之前。我不确定这里的意图是在文件开头放置一个空行,还是在文件之间放置三个空行(开头一个,结尾两个),所以我不确定到底是什么适当的替换是。无论如何,在解决所有这些问题后,我可以解决以下问题:

    #!/bin/sh
    OUTPUT="../best_practices.textile"
    FILES=(../best-practices/*.textile)
    
    : >"$OUTPUT"
    for f in "${FILES[@]}"
    do
      echo "Processing $f file..."
      echo >>"$OUTPUT"
    
      while IFS='' read -r line; do 
        printf "%s\n" "$line">>"$OUTPUT"
      done <"$f"
    
      echo >>"$OUTPUT"
      echo >>"$OUTPUT"
    done
    

    【讨论】:

    • 感谢您花时间研究这个 Gordon,它提供了非常丰富的信息。
    • 最佳答案在这里!非常感谢您花时间这么清楚地解释。 :)
    • 我是否认为IFS='' read -r line 只影响read 命令使用的内部字段分隔符,而不影响while 循环内部或之后的任何内容?我见过ORIG_IFS="$IFS"; ...; IFS="$ORIG_IFS",但在这种情况下这不是必需的,是吗?
    • @davidchambers 正确,因为赋值被用作read 命令的前缀,它只适用于那个命令。
    • 谢谢,@GordonDavisson。
    【解决方案2】:

    代替:

    cat $f | while read line; do 
        echo "$line">>$OUTPUT
    done
    

    这样做:

    cat $f >>$OUTPUT
    

    (如果有理由需要逐行执行,最好将其包含在问题中。)

    【讨论】:

    • 这也杀死了空白。我逐行切换,看看它是否会给我更多节省前导空间的选择。
    • 有趣。这个答案已经被否决了两次,没有任何解释。如果您要投反对票,请说出原因。 (如果是因为您认为“这可能只是一个 cat 命令”:1. 不是真的,请注意在文件和 2 之间插入的额外空行。我假设(可能不正确)这是一个精简的脚本为了简单起见,真实版本可能有一些额外的每个文件逻辑。)
    • 我确信我们可以通过某种方式查看谁投了反对票。如果不是,我不明白为什么不能强制要求在否决时发表评论。
    • @ehime 请详细说明。
    【解决方案3】:

    这是一种过于昂贵的文件组合方式。

    cat ../best-practices/*.textile >  ../best_practices.textile
    

    如果你想在连接时为每个文件添加一个空白(换行符),请使用 awk

    awk 'FNR==1{print "">"out.txt"}{print > "out.txt" }' *.textile
    

    awk 'FNR==1{print ""}{print}' file* > out.txt
    

    【讨论】:

    • 天啊,你比我快 1 秒。
    • 不错。谢谢你。从概念上讲,我真的很喜欢 Bash。现在我只需要让我的知识符合我的感情。干杯
    • 我的文件标题让我觉得很讽刺......呵呵
    • @Joel:永远记住它的名字是 conCATenate -
    【解决方案4】:

    这允许您在每个输入文件之间穿插换行符,就像您在原始脚本中所做的那样:

    for f in $FILES; do echo -ne '\n\n' | cat "$f" -; done > $OUTPUT
    

    请注意,$FILES 未加引号以使其正常工作(否则额外的换行符仅在所有输出的末尾出现一次),但必须引用 $f 以保护文件名中的空格(如果存在)。

    【讨论】:

      【解决方案5】:

      正确答案,imo,是this,转载如下:

      while IFS= read line; do
          check=${line:0:1}
      done < file.txt
      

      请注意,它会处理输入来自另一个命令的情况,而不仅仅是来自实际文件。

      请注意,您还可以简化重定向,如下所示。

      #!/bin/bash
      OUTPUT="../best_practices.textile"
      FILES="../best-practices/*.textile"
      for f in "$FILES"
      do
        echo "Processing $f file..."
        {
        echo
      
        while IFS= read line; do 
            echo "$line"
        done < $f
        echo
        echo;
        } > $OUTPUT
      done
      

      【讨论】:

        猜你喜欢
        • 2011-11-10
        • 2014-01-13
        • 2011-12-09
        • 1970-01-01
        • 1970-01-01
        • 2019-09-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多