为啥 sed 不将 \t 识别为制表符?

Posted

技术标签:

【中文标题】为啥 sed 不将 \\t 识别为制表符?【英文标题】:Why is sed not recognizing \t as a tab?为什么 sed 不将 \t 识别为制表符? 【发布时间】:2011-02-06 07:06:34 【问题描述】:
sed "s/\(.*\)/\t\1/" $filename > $sedTmpFile && mv $sedTmpFile $filename

我希望这个sed 脚本在$filename 的每一行前面插入一个tab,但事实并非如此。出于某种原因,它改为插入t

【问题讨论】:

由于 sed 因平台而异(特别是 BSD/MacOSX 与 Linux),因此指定您使用 sed 的平台可能会有所帮助。 sed "s/(.*)/#\1/" $filename | tr '#' '\t' > $sedTmpFile && mv $sedTmpFile $filename. 对于 OS X (macOS) 用户,请参阅this question。 见unix.stackexchange.com/a/145385/199866 【参考方案1】:

并非所有版本的sed 都能理解\t。只需插入一个文字标签(按 Ctrl-V 然后按 Tab)。

【讨论】:

啊,是的;澄清一下:并非所有版本的 sed 都能在表达式的替换部分理解 \t(它在模式匹配部分识别 \t 就好了) awwwwwwwwwwwwwwwwwww,好吧,这很有趣。而且很奇怪。你为什么要让它在一个地方识别它而不是另一个......? 从脚本调用,这不起作用:选项卡将被 sh 忽略。例如,来自 shell 脚本的以下代码将添加 $TEXT_TO_ADD,而无需在前面添加制表符: sed "$LINEa\\ $TEXT_TO_ADD " $FILE . @Dereckson 和其他人 - 看到这个答案:***.com/a/2623007/48082 德雷克森 s/can/can't/ ?【参考方案2】:

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

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

【讨论】:

您在$'string' 上走在正确的轨道上,但缺乏解释。事实上,我怀疑,由于非常尴尬的用法,您可能没有完全理解(就像我们大多数人对 bash 所做的那样)。请参阅下面的解释:***.com/a/43190120/117471 请记住,BASH 不会在单引号内扩展像 $TAB 这样的变量,因此您需要使用双引号。 小心在双引号内使用*...这将被视为一个全局变量,而不是您想要的正则表达式。【参考方案3】:

@sedit 是在正确的道路上,但定义一个变量有点尴尬。

解决方案(特定于 bash)

在 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' 形式的单词被特殊处理。词 展开 到 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。这是由 The Open Group http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16 解释的,这就是为什么试图避免 bashism 通常会失败的一个例子。

【讨论】:

【参考方案4】:

我在 Ubuntu 12.04 (LTS) 上使用了类似的 Bash shell:

first 匹配时,用 tab,second 追加一个新行:

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

tab,second替换first

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

【讨论】:

双重转义是关键,即使用\\t 而不是\t 我还必须在 Ubuntu 16.04 和 Bash 4.3 上使用双引号而不是单引号。【参考方案5】:

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

例如。要删除标签:

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

【讨论】:

有趣的是,您正在使用“GNU echo”特定功能(将 \t 解释为制表符)来解决“BSD sed”特定错误(将 \t 解释为 2 个单独的字符)。据推测,如果你有“GNU echo”,你也会有“GNU sed”。在这种情况下,您不需要使用 echo。使用 BSD echo echo '\t' 将输出 2 个单独的字符。 POSIX 可移植方式是使用printf '\t'。这就是为什么我说:不要试图通过不使用 bash 来使代码可移植。这比你想象的要难。使用bash 是我们大多数人可以做的最便携的事情。【参考方案6】:

您不需要使用sed 进行替换,而实际上您只想在行前插入一个制表符。与仅打印出来相比,替换这种情况是一项昂贵的操作,尤其是在处理大文件时。它也更容易阅读,因为它不是正则表达式。

例如使用 awk

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

【讨论】:

【参考方案7】:

我在 Mac 上使用过这个:

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

Used this link for reference

【讨论】:

【参考方案8】:

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:])

【讨论】:

Perl 版本将是 shell 单行 "perl -pe 's/a/b/' 文件名" 或 "something | perl -pe 's/a/b/'" 【参考方案9】:

我使用 perl 代替 BSD sed:

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

【讨论】:

【参考方案10】:

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

1) 如果 OP 想要使用类似于他们最初提出的搜索和替换方法,那么我建议为此使用perl,如下所示。 注意: 正则表达式括号前的反斜杠不是必需的,并且此代码行反映了 $1 如何比 \1 更好地使用 perl 替换运算符(例如每个 Perl 5 documentation) .

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

2) 但是,正如ghostdog74 所指出的,因为所需的操作实际上是在将 tmp 文件更改为输入/目标文件之前,在每行的开头简单地添加一个制表符 ($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

【讨论】:

【参考方案11】:
TAB=$(printf '\t')
sed "s/$TAB//g" input_file

它适用于 Red Hat,它会从输入文件中删除标签。

【讨论】:

【参考方案12】:

如果你知道某些字符没有被使用,你可以把“\t”翻译成别的东西。 猫我的文件 | tr "\t" "," | sed "s/(.*)/,\1/"

【讨论】:

以上是关于为啥 sed 不将 \t 识别为制表符?的主要内容,如果未能解决你的问题,请参考以下文章

为什么sed没有将 t识别为标签?

如何让linux中的空格和制表符原形毕露

linux 中删除第一个空格或者制表符之前的所有内容

如何在 BASH 中将制表符分隔值 (TSV) 文件转换为逗号分隔值 (CSV) 文件?

C语言字符串中\t为啥是四个空格

关于 水平制表符 Horizontal Tab (TAB)