Why is sed not recognizing \t as a tab?

sed "s/\(.*\)/\t\1/" $filename > $sedTmpFile && mv $sedTmpFile $filename

I am expecting this sed script to insert a tab in front of every line in $filename however it is not. For some reason it is inserting a t instead.

130362 次浏览

Not all versions of sed understand \t. Just insert a literal tab instead (press Ctrl-V then Tab).

sed不支持 \t,也不支持像 \n这样的转义序列。我发现实现这一点的唯一方法是使用 sed在脚本中实际插入制表符。

也就是说,您可能需要考虑使用 Perl 或 Python。下面是我编写的一个用于所有流正则化的简短 Python 脚本:

#!/usr/bin/env python
import sys
import re


def main(args):
if len(args) < 2:
print >> sys.stderr, 'Usage: <search-pattern> <replace-expr>'
raise SystemExit


p = re.compile(args[0], re.MULTILINE | re.DOTALL)
s = sys.stdin.read()
print p.sub(args[1], s),


if __name__ == '__main__':
main(sys.argv[1:])

实际上,您不需要使用 sed进行替换,只需要在行前插入一个选项卡即可。与仅仅打印出来相比,替换这种情况是一种昂贵的操作,特别是在处理大文件时。因为它不是 regex,所以更容易阅读。

(使用 awk)

awk '{print "\t"$0}' $filename > temp && mv temp $filename

使用 Bash,你可以像下面这样以编程方式插入 TAB 字符:

TAB=$'\t'
echo 'line' | sed "s/.*/${TAB}&/g"
echo 'line' | sed 's/.*/'"${TAB}"'&/g'   # use of Bash string concatenation

我曾经在 Ubuntu 12.04(LTS)的 Bash shell 中使用过类似的东西:

第一匹配时用 Tab 第二附加新行:

sed -i '/first/a \\t second' filename

Tab 第二代替 第一:

sed -i 's/first/\\t second/g' filename

使用 $(echo '\t')。您需要在模式周围引用。

Eg. To remove a tab:

sed "s/$(echo '\t')//"

我在 Mac 上用过这个:

sed -i '' $'$i\\\n\\\thello\n' filename

Used this link for reference

@ sedit 的方法是正确的,但是定义一个变量有点麻烦。

Solution (bash specific)

在 bash 中执行此操作的方法是在单引号字符串前面放置一个美元符号。

$ echo -e '1\n2\n3'
1
2
3


$ echo -e '1\n2\n3' | sed 's/.*/\t&/g'
t1
t2
t3


$ echo -e '1\n2\n3' | sed $'s/.*/\t&/g'
1
2
3

如果你的字符串需要包含变量展开式,你可以像这样把带引号的字符串放在一起:

$ timestamp=$(date +%s)
$ echo -e '1\n2\n3' | sed "s/.*/$timestamp"$'\t&/g'
1491237958  1
1491237958  2
1491237958  3

解释

在 bash 中,$'string'会导致“ ANSI-C 扩展”。而这正是我们大多数人在使用像 \t\r\n这样的东西时所期望的。发信人: https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html#ANSI_002dC-Quoting

$“ string”形式的单词被特别处理。单词扩展 指定的反斜杠转义字符替换为 绳子 ANSI C 标准。反斜杠转义序列,如果存在,是 解码。

扩展的结果是单引号,好像美元符号没有 一直在场。

解决方案(如果必须避免 bash)

我个人认为大多数避免 bash 的努力都是愚蠢的,因为避免 bashism 并不能使您的代码具有可移植性。(如果您将代码设置为 bash -eu,那么它将比试图避免 bash 并使用 sh(除非您是一个绝对的 POSIX 忍者)的代码更加脆弱。)但与其在这个问题上进行宗教辩论,我还是给你们一个最好的答案。

$ echo -e '1\n2\n3' | sed "s/.*/$(printf '\t')&/g"
1
2
3

最佳答案 * ?是的,因为大多数反 bash shell 脚本会在代码中出错的一个例子就是使用 echo '\t',就像在 @robrecord's answer中一样。这对 GNU echo 有效,但对 BSD echo 无效。这是由 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16的开放小组解释的,这就是为什么试图避免抨击通常会失败的一个例子。

我使用 perl 代替 BSD sed:

ct@MBA45:~$ python -c "print('\t\t\thi')" |perl -0777pe "s/\t/ /g"
hi

我认为其他人已经为其他方法(sedAWK等)充分澄清了这一点。然而,我的 bash特定答案(在 macOS High Sierra 和 CentOS 6/7上测试)如下。

1)如果 OP 想使用类似于他们最初提议的搜索和替换方法,那么我建议使用 perl,如下所示。正则表达式的括号前面的 Notes:反斜杠应该是不必要的,这一行代码反映了如何使用 $1比使用 perl替换操作符的 \1更好(例如,每个 Perl 5文档)。

perl -pe 's/(.*)/\t$1/' $filename > $sedTmpFile && mv $sedTmpFile $filename

2)然而,正如 Ghostdog74指出的那样,由于所需的操作实际上是在将 tmp 文件更改为 input/target 文件($filename)之前,在每行的开头简单地执行 再加个账单,所以我再次推荐使用 perl,但需要进行以下修改:

perl -pe 's/^/\t/' $filename > $sedTmpFile && mv $sedTmpFile $filename
## OR
perl -pe $'s/^/\t/' $filename > $sedTmpFile && mv $sedTmpFile $filename

3)当然,tmp 文件是 多余的,所以最好只是“就位”(添加 -i标志) ,并简化为一个更优雅的一行程序

perl -i -pe $'s/^/\t/' $filename
TAB=$(printf '\t')
sed "s/${TAB}//g" input_file

我在 Red Hat 上使用它,它可以从输入文件中删除选项卡。

如果您知道某些字符没有被使用,您可以将“ t”翻译成其他字符。 Cat my _ file | tr“ t”,”| sed“ s/(. *)/,1/”