126
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.

user664833
  • 18,397
  • 19
  • 91
  • 140
sixtyfootersdude
  • 25,859
  • 43
  • 145
  • 213

12 Answers12

151

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

Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
  • 3
    Ah yes; to clarify: not all versions of sed understand `\t` in the replacement part of the expression (it recognized `\t` in the pattern matching part just fine) – John Weldon Apr 09 '10 at 19:07
  • 3
    awwwwwwwwwwwwwwwwwww, ok that is pretty interesting. And strange. Why would you make it recognize it in one place but not the other...? – sixtyfootersdude Apr 09 '10 at 19:09
  • 3
    Called from a script, that won't work: tabs would be ignored by sh. For example, the following code from a shell script will add $TEXT_TO_ADD, without prepending it by a tabulation: sed "${LINE}a\\ $TEXT_TO_ADD " $FILE . – Dereckson Jan 23 '13 at 21:16
  • 2
    @Dereckson and others - see this answer: http://stackoverflow.com/a/2623007/48082 – Cheeso Jun 29 '13 at 01:50
  • @Cheeso The question is about sed, not about the use of a specific shell. To use bash isn't an universal solution: you can expect it installed on a out-of-the box BSD machine for example. – Dereckson Aug 20 '13 at 12:19
  • 2
    Dereckson s/can/can't/ ? – Douglas Held Jan 25 '15 at 18:21
  • 2
    Please only do this if you absolutely must avoid bash! Otherwise it is terrible to encode a literal tab. Even then you should use `$(printf '\t')` which (unlike echo) **is POSIX portable**. See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16 – Bruno Bronosky Apr 03 '17 at 17:25
  • 1
    Note that `$(printf \\t)` is equivalent to `$(printf '\t')` because of how the shell treats the backslash escape character. I just find it a little clearer to read sometimes without the nested quotes. Note also that `sed "s/\(.*\)/$(printf \\t)\1/"` works but `sed 's/\(.*\)/$(printf \\t)\1/'` doesn't, because the shell only expands and starts subshells between double quote `"`, not single quote `'`. – Zack Morris Aug 15 '18 at 20:22
  • 1
    See @BrunoBronosky 's answer below! – user664833 Jun 11 '20 at 15:23
48

Using Bash you may insert a TAB character programmatically like so:

TAB=$'\t' 
echo 'line' | sed "s/.*/${TAB}&/g" 
echo 'line' | sed 's/.*/'"${TAB}"'&/g'   # use of Bash string concatenation
sedit
  • 489
  • 3
  • 2
  • 1
    You were on the right track with the `$'string'` but lack explanation. In fact I suspect, because of the extremely awkward usage that you probably have an incomplete understanding (as most of us do with bash). See my explanation below: http://stackoverflow.com/a/43190120/117471 – Bruno Bronosky Apr 03 '17 at 16:53
  • 2
    Remember that BASH won't expand variables like `$TAB` inside single quotes, so you'll need to use it double quotes. – nealmcb Jun 07 '18 at 14:37
  • Careful about using `*` inside double quotes... this will be treated as a glob, not as the regex you intend. – levigroker Oct 24 '19 at 00:54
31

@sedit was on the right path, but it's a bit awkward to define a variable.

Solution (bash specific)

The way to do this in bash is to put a dollar sign in front of your single quoted string.

$ 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

If your string needs to include variable expansion, you can put quoted strings together like so:

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

Explanation

In bash $'string' causes "ANSI-C expansion". And that is what most of us expect when we use things like \t, \r, \n, etc. From: https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html#ANSI_002dC-Quoting

Words of the form $'string' are treated specially. The word expands to string, with backslash-escaped characters replaced as specified by the ANSI C standard. Backslash escape sequences, if present, are decoded...

The expanded result is single-quoted, as if the dollar sign had not been present.

Solution (if you must avoid bash)

I personally think most efforts to avoid bash are silly because avoiding bashisms does NOT* make your code portable. (Your code will be less brittle if you shebang it to bash -eu than if you try to avoid bash and use sh [unless you are an absolute POSIX ninja].) But rather than have a religious argument about that, I'll just give you the BEST* answer.

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

* BEST answer? Yes, because one example of what most anti-bash shell scripters would do wrong in their code is use echo '\t' as in @robrecord's answer. That will work for GNU echo, but not BSD echo. That is explained by The Open Group at http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16 And this is an example of why trying to avoid bashisms usually fail.

Bruno Bronosky
  • 66,273
  • 12
  • 162
  • 149
9

I've used something like this with a Bash shell on Ubuntu 12.04 (LTS):

To append a new line with tab,second when first is matched:

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

To replace first with tab,second:

sed -i 's/first/\\t second/g' filename
Thomas Bratt
  • 48,038
  • 36
  • 121
  • 139
5

Use $(echo '\t'). You'll need quotes around the pattern.

Eg. To remove a tab:

sed "s/$(echo '\t')//"
ByteHamster
  • 4,884
  • 9
  • 38
  • 53
robrecord
  • 504
  • 5
  • 15
  • 6
    It's funny that you are using a "GNU echo" specific feature (interpreting \t as a tab character) to solve a "BSD sed" specific bug (interpreting \t as 2 separate characters). Presumably, if you have "GNU echo" you would also have "GNU sed". In which case you would not need to use echo. With BSD echo `echo '\t'` is going to output 2 separate characters. The POSIX portable way is to use `printf '\t'`. This is why I say: Don't try to make your code portable by not using bash. It's harder than you think. Using `bash` is the most portable thing most of us can do. – Bruno Bronosky Apr 03 '17 at 17:39
3

You don't need to use sed to do a substitution when in actual fact, you just want to insert a tab in front of the line. Substitution for this case is an expensive operation as compared to just printing it out, especially when you are working with big files. Its easier to read too as its not regex.

eg using awk

awk '{print "\t"$0}' $filename > temp && mv temp $filename
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
2

I used this on Mac:

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

Used this link for reference

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
Raj Hassani
  • 1,577
  • 1
  • 19
  • 26
0

sed doesn't support \t, nor other escape sequences like \n for that matter. The only way I've found to do it was to actually insert the tab character in the script using sed.

That said, you may want to consider using Perl or Python. Here's a short Python script I wrote that I use for all stream regex'ing:

#!/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:])
Roman Nurik
  • 29,665
  • 7
  • 84
  • 82
  • 2
    And the Perl version would be the shell one-liner "perl -pe 's/a/b/' filename" or "something | perl -pe 's/a/b/'" – tiftik Apr 09 '10 at 20:36
0

Instead of BSD sed, i use perl:

ct@MBA45:~$ python -c "print('\t\t\thi')" |perl -0777pe "s/\t/ /g"
   hi
Cees Timmerman
  • 17,623
  • 11
  • 91
  • 124
0

I think others have clarified this adequately for other approaches (sed, AWK, etc.). However, my bash-specific answers (tested on macOS High Sierra and CentOS 6/7) follow.

1) If OP wanted to use a search-and-replace method similar to what they originally proposed, then I would suggest using perl for this, as follows. Notes: backslashes before parentheses for regex shouldn't be necessary, and this code line reflects how $1 is better to use than \1 with perl substitution operator (e.g. per Perl 5 documentation).

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

2) However, as pointed out by ghostdog74, since the desired operation is actually to simply add a tab at the start of each line before changing the tmp file to the input/target file ($filename), I would recommend perl again but with the following modification(s):

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

3) Of course, the tmp file is superfluous, so it's better to just do everything 'in place' (adding -i flag) and simplify things to a more elegant one-liner with

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

It works for me on Red Hat, which will remove tabs from the input file.

Dayong
  • 5,614
  • 1
  • 14
  • 6
0

If you know that certain characters are not used, you can translate "\t" into something else. cat my_file | tr "\t" "," | sed "s/(.*)/,\1/"