如何在目录的每个文件中用空格替换选项卡

时间:2014-06-24 15:00:56

标签: sed

我想用对应的空格替换目录的每个文件中的选项卡。我找到了一个解决方案11094383,您可以用给定个空格数替换制表符:

> find ./ -type f -exec sed -i 's/\t/     /g' {} \;

在上面的解决方案中,标签被四个空格替换。但在我的情况下,标签可以占用更多的空间 - 例如8.

带有标签的文件示例,应该用8个空格替换:

NSMl1        100  PSHELL 0.00260  400000  400200  400300
          400400  400500  400600  400700  400800  400900
      401000  401100  400100  430000  430200  430300
      430400  430500  430600  430700  430800  430900
      431000  431100  430100  401200  431200

此处带有制表符的行是第3行到第5行。

带有标签的文件示例,应该用4个标签替换:

RBE2     1101001 5000511  123456    1100

有人可以帮忙吗?

1 个答案:

答案 0 :(得分:1)

经典的答案是使用带有选项的pr命令将标签扩展到适当数量的空格,转换分页功能:

pr -e8 -l1 -t …files…

棘手的部分是让文件覆盖,这似乎是问题的一部分。当然,GNU和BSD(Mac OS X)版本中的sed支持使用-i选项覆盖 - 两者之间存在变体行为,因为BSD sed需要备份文件的后缀和GNU sed没有。但是,sed并不(很容易)支持将标签转换为适当数量的空白,因此它并不完全合适。

The UNIX Programming Environment中有一个脚本overwrite(我缩写为ow)可以做到这一点。我自1987年以来一直在使用该脚本(首次登记 - 最后一次更新于2005年)。

#!/bin/sh
#       Overwrite file
#       From: The UNIX Programming Environment by Kernighan and Pike
#       Amended: remove PATH setting; handle file names with blanks.

case $# in
0|1)    echo "Usage: $0 file command [arguments]" 1>&2
        exit 1;;
esac

file="$1"
shift
new=${TMPDIR:-/tmp}/ovrwr.$$.1
old=${TMPDIR:-/tmp}/ovrwr.$$.2

trap "rm -f '$new' '$old' ; exit 1" 0 1 2 15

if "$@" >"$new"
then
    cp "$file" "$old"
    trap "" 1 2 15
    cp "$new" "$file"
    rm -f "$new" "$old"
    trap 0
    exit 0
else
    echo "$0: $1 failed - $file unchanged" 1>&2
    rm -f "$new" "$old"
    trap 0
    exit 1
fi

这些天在大多数系统上使用mktemp命令是可能的,也可以说更好;它当时并不存在。

在问题的上下文中,您可以使用:

find . -type f -exec ow {} pr -e8 -t -l1 \;

您需要单独处理每个文件。

如果您真的决定使用sed来完成工作,那么您的工作就会被删除。有一种可怕的方式来做到这一点。有一个符号问题;如何表示文字标签;我将使用\t来表示它。该脚本将存储在一个文件中,我假设它是script.sed

:again
/^\(\([^\t]\{8\}\)*\)\t/s//\1        /
/^\(\([^\t]\{8\}\)*\)\([^\t]\{1\}\)\t/s//\1\3       /
/^\(\([^\t]\{8\}\)*\)\([^\t]\{2\}\)\t/s//\1\3      /
/^\(\([^\t]\{8\}\)*\)\([^\t]\{3\}\)\t/s//\1\3     /
/^\(\([^\t]\{8\}\)*\)\([^\t]\{4\}\)\t/s//\1\3    /
/^\(\([^\t]\{8\}\)*\)\([^\t]\{5\}\)\t/s//\1\3   /
/^\(\([^\t]\{8\}\)*\)\([^\t]\{6\}\)\t/s//\1\3  /
/^\(\([^\t]\{8\}\)*\)\([^\t]\{7\}\)\t/s//\1\3 /
t again

使用经典的sed符号。

然后你可以写:

sed -f script.sed …data-files…

如果您有GNU sed或BSD(Mac OS X)sed,则可以使用扩展正则表达式:

:again
/^(([^\t]{8})*)\t/s//\1        /
/^(([^\t]{8})*)([^\t]{1})\t/s//\1\3       /
/^(([^\t]{8})*)([^\t]{2})\t/s//\1\3      /
/^(([^\t]{8})*)([^\t]{3})\t/s//\1\3     /
/^(([^\t]{8})*)([^\t]{4})\t/s//\1\3    /
/^(([^\t]{8})*)([^\t]{5})\t/s//\1\3   /
/^(([^\t]{8})*)([^\t]{6})\t/s//\1\3  /
/^(([^\t]{8})*)([^\t]{7})\t/s//\1\3 /
t again

然后运行:

sed -r -f script.sed …data-files…    # GNU sed
sed -E -f script.sed …data-files…    # BSD sed

脚本有什么作用?

第一行设置标签;如果中间的任何s///操作进行了替换,则最后一行跳转到该标签。因此,对于文件的每一行,脚本循环直到没有匹配,因此不执行替换。

8次换人涉及:

  • 捕获的8个非标签的零个或多个序列的块,然后是
  • 一系列0-7个非标签,也被捕获,然后是
  • 一个标签。
  • 它取代了与捕获材料的匹配,然后是适当数量的空格。

测试期间发现的一个好奇心是,如果一行以空格结尾,pr命令会删除该尾随空格。

在某些系统(至少是BSD或Mac OS X)上还有expand命令,它保留了尾随的空白区域。使用它比prsed更简单。

使用这些sed脚本,并将BSD或GNU sed与备份文件一起使用,您可以写:

find . -type f -exec sed -i.bak -r -f script.sed {} +

(GNU sed表示法;将-E替换为-r替换BSD sed。)