0

I have a csv file like this:

0,0,1
1,1,0

Notice that it does NOT have column names with it. Write the column names at the beginning? I looked at the question here but this doesn't work on my Mac OSx: sed '1s/^/<column names>\n&/g'. It just adds the column names, and an n appended to them. How do I achieve this?

Community
  • 1
  • 1
makansij
  • 9,303
  • 37
  • 105
  • 183
  • 1
    Possible duplicate of [prepend to a file one liner shell?](http://stackoverflow.com/questions/54365/prepend-to-a-file-one-liner-shell) – Jordan Running Nov 29 '15 at 23:46

2 Answers2

4

If you really just want to prepend a line to an existing file, cat is all you need:

echo 'ColHdr1,ColHdr2,ColHdr3' | cat - file.csv > /tmp/$$ && mv /tmp/$$ file.csv

Or, more efficiently, but perhaps more obscurely, using a group command:

{ echo 'ColHdr1,ColHdr2,ColHdr3'; cat file.csv; } > /tmp/$$ && mv /tmp/$$ file.csv

Note the need to write to a temp. file first, because the shell doesn't support reading from and writing back to the same file using simple output redirection with > (the output file is truncated before the command executes).


That said, shellter's answer points to a more convenient solution, using sed's -i option for (limited) in-place updating (if supported):

  • OS X and BSD systems (assumes bash, ksh, or zsh):

    sed -i '' $'1i\\\nColHdr1,ColHdr2,ColHdr3\n' file.csv
    
  • Linux (GNU sed):

    sed -i  '1iColHdr1,ColHdr2,ColHdr3' file.csv
    

-i for in-place updating is a nonstandard extension to the POSIX standard, and the way it is implemented carries risks, most notably the potential destruction of a symlink. The same applies to the > /tmp/$$ && mv /tmp/$$ ... approach above.


As for why your approach didn't work:

sed '1s/^/<column names>\n&/' # Fails on OS X: Sed doesn't support \n in the replacement

As an aside: using the g option for global matching makes no sense, since you're matching the beginning of the line (^), so there can only be one match by definition.

OS X uses BSD Sed, which doesn't support control-character escape sequences in the replacement part of the s function call (unlike GNU Sed, which does).

As in the solution above, you could work around the problem with an ANSI C-quoted string ($'...') in bash, ksh, or zsh:

sed $'1s/^/<column names>\\\n&/' file.csv

Note the extra \\, which is needed to \-escape the literal newline that the \n expands to by virtue of the ANSI C-quoted string. (Escaping is needed, because a literal newline normally terminates a function call.)

Alternatively use literal newlines in your Sed script - but note that \-escaping the newline is still needed:

sed '1s/^/<column names>\
&/' file.csv

The above is what the ANSI C-quoted string solution effectively expands to.

If you want to learn about all the differences between the more flexible GNU Sed and BSD Sed, see my answer here.

Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775
1

Any sed will honor the i\ (insert) command.

Try

 sed '1i\
        ColHdr1,ColHdr2,ColHdr3

 ' csv.file

output

ColHdr1,ColHdr2,ColHdr3
0,0,1
1,1,0

Newer seds don't required that you have the input on a following line, hence for some systems

sed '1i\ColHdr1,ColHdr2,ColHdr3

 ' csv.file

Because you can use the i\ to insert multiple lines into a file, you have to signal the end of input with a blank line.

IHTH

shellter
  • 36,525
  • 7
  • 83
  • 90
  • Again, this doesn't quite work: `: extra characters after \ at the end of i command` – makansij Nov 30 '15 at 01:39
  • 1
    remove any trailing white space after the '\' char. Good luck. – shellter Nov 30 '15 at 01:42
  • Nicely done; however, a _single_ trailing newline is enough (and not even needed here with _GNU_ Sed); if you truly wanted to insert _multiple_ lines with `i`, the interior newlines would have to be `\ `-escaped - the first _unescaped_ newline terminates `i`'s argument. – mklement0 Nov 30 '15 at 02:30