经典答案是使用带有选项的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 命令,它保留了尾随空格。使用它比pr 或sed 更简单。
使用这些sed 脚本,并使用带有备份文件的BSD 或GNU sed,您可以编写:
find . -type f -exec sed -i.bak -r -f script.sed {} +
(GNU sed 表示法;用 -E 代替 -r 代替 BSD sed。)