sed "s/\(.*\)/\t\1/" $filename > $sedTmpFile && mv $sedTmpFile $filename
我希望这个 sed
脚本在 $filename
的每一行前面插入一个 tab
,但事实并非如此。出于某种原因,它改为插入 t
。
并非所有版本的 sed
都能理解 \t
。只需插入一个文字标签(按 Ctrl-V 然后按 Tab)。
使用 Bash,您可以像这样以编程方式插入 TAB 字符:
TAB=$'\t'
echo 'line' | sed "s/.*/${TAB}&/g"
echo 'line' | sed 's/.*/'"${TAB}"'&/g' # use of Bash string concatenation
$'string'
走在正确的轨道上,但缺乏解释。事实上,我怀疑,由于非常尴尬的用法,您可能对它的理解不完全(就像我们大多数人对 bash 所做的那样)。请参阅下面的解释:stackoverflow.com/a/43190120/117471
$TAB
这样的变量,因此您需要使用双引号。
*
...这将被视为一个 glob,而不是您想要的正则表达式。
@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' 形式的单词被特殊处理。该单词扩展为字符串,并按照 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 脚本编写者在他们的代码中会做错的一个例子是在 @robrecord's answer 中使用 echo '\t'
。这适用于 GNU echo,但不适用于 BSD echo。 http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16 的 The Open Group 对此进行了解释,这就是为什么试图避免 bashism 通常会失败的一个例子。
我在 Ubuntu 12.04 (LTS) 上使用了类似的 Bash shell:
用制表符追加一个新行,第二个匹配第一个时:
sed -i '/first/a \\t second' filename
用制表符替换第一个,第二个:
sed -i 's/first/\\t second/g' filename
\\t
而不是 \t
。
使用 $(echo '\t')
。您需要在模式周围加上引号。
例如。要删除选项卡:
sed "s/$(echo '\t')//"
echo '\t'
将输出 2 个单独的字符。 POSIX 可移植方式是使用 printf '\t'
。这就是为什么我说:不要试图通过不使用 bash 来使代码可移植。这比你想象的要难。使用 bash
是我们大多数人可以做的最便携的事情。
实际上,您不需要使用 sed
进行替换,您只需在行前插入一个制表符。与仅打印出来相比,替换这种情况是一项昂贵的操作,尤其是在处理大文件时。它也更容易阅读,因为它不是正则表达式。
例如使用 awk
awk '{print "\t"$0}' $filename > temp && mv temp $filename
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 而不是 BSD sed:
ct@MBA45:~$ python -c "print('\t\t\thi')" |perl -0777pe "s/\t/ /g"
hi
我认为其他人已经为其他方法(sed
、AWK
等)充分阐明了这一点。但是,我的 bash
特定答案(在 macOS High Sierra 和 CentOS 6/7 上测试)如下。
1)如果 OP 想要使用类似于他们最初提出的搜索和替换方法,那么我建议使用 perl
,如下所示。 注意: 正则表达式的括号前的反斜杠不是必需的,并且此代码行反映了 $1
如何比带有 perl
替换运算符的 \1
更好地使用(例如每个 Perl 5 documentation) .
perl -pe 's/(.*)/\t$1/' $filename > $sedTmpFile && mv $sedTmpFile $filename
2) 然而,正如 ghostdog74 所指出的,因为所需的操作实际上是在将 tmp 文件更改为输入/目标文件之前,在每行的开头简单地添加一个制表符({2 }),我会再次推荐 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”翻译成其他字符。猫我的文件 | tr "\t" "," | sed "s/(.*)/,\1/"
\t
(它在模式匹配部分中识别出\t
就好了)