20

Specifically, I'm using a combination of >> and tee in a custom alias to store new Homebrew updates in a text file, as well as output on screen:

alias bu="echo `date "+%Y-%m-%d at %H:%M"` \
    >> ~/Documents/Homebrew\ Updates.txt && \
    brew update | tee -a ~/Documents/Homebrew\ Updates.txt"

Question: What if I wish to prepend this output in my textfile, i.e. placed at the beginning of the file as opposed to appending it to the end?


Edit1: As someone reported in the answers below, the use of temp files might be a good approach, which at least helped me partially:

targetLog="~/Documents/Homebrew\ Updates.txt"
alias bu="(brew update | cat - $targetLog \
> /tmp/out1 && mv /tmp/out1 $targetLog \
&& echo `date "+%Y-%m-%d at %H:%M":%S` | \
cat - $targetLog > /tmp/out2 \
&& mv /tmp/out2 $targetLog)"

But the problem is the output to STDOUT (previously made possible by tee), which I'm not sure can be incorporated in this tempfile approach …?

wonder
  • 312
  • 1
  • 3
  • 14
Henrik
  • 2,421
  • 4
  • 25
  • 33
  • [this post](http://stackoverflow.com/questions/54365/prepend-to-a-file-one-liner-shell) should help you, use search before posting new question –  Oct 18 '11 at 12:14

7 Answers7

20

sed will happily do that for you, using -i to edit in place, eg.

sed -i -e "1i `date "+%Y-%m-%d at %H:%M"`" some_file
Hasturkun
  • 35,395
  • 6
  • 71
  • 104
17

This works by creating an output file:

Let's say we have the initial contents on file.txt

echo "first line" > file.txt          
echo "second line" >> file.txt

So, file.txt is our 'bottom' text file. Now prepend into a new 'output' file

echo "add new first line" | cat - file.txt > output.txt # <--- Just this command

Now, output has the contents the way we want. If you need your old name:

mv output.txt file.txt
cat file.txt
mimoralea
  • 9,590
  • 7
  • 58
  • 59
  • sed behaves differently between mac and linux so this version is good if you need to avoid sed for that reason. – cd3k Mar 09 '23 at 12:45
5

The only simple and safe way to modify an input file using bash tools, is to use a temp file, eg. sed -i uses a temp file behind the scenes (but to be robust sed needs more).

Some of the methods used have a subtle "can break things" trap, when, rather than running your command on the real data file, you run it on a symbolic link (to the file you intend to modify). Unless catered for correctly, this can break the link and convert it into a real file which receives the mods and leaves the original real file without the intended mods and without the symlink (no error exit-code results)

To avoid this with sed, you need to use the --follow-symlinks option.
For other methods, just be aware that it needs to follow symlinks (when you act on such a link)
Using a temp file, then rm temp file works only if "file" is not a symlink.

One safe way is to use sponge from package moreutils

Unlike a shell redirect, sponge soaks up all its input before opening the output file. This allows for constructing pipelines that read from and write to the same file.

sponge is a good general way to handle this type of situation.

Here is an example, using sponge

hbu=~/'Documents/Homebrew Updates.txt'
{ date "+%Y-%m-%d at %H:%M"; cat "$hbu"; } | sponge "$hbu"
Peter.O
  • 6,696
  • 4
  • 30
  • 37
  • How/where do I get sponge if I'm using Darwin UNIX (OS X)? – Henrik Oct 18 '11 at 16:26
  • How does changing the inode make a _symbolic_ link invalid? I assume you mean it invalidates hard links. – Mark Edgar Oct 18 '11 at 20:43
  • 1
    *@Mark Edgar:* I had it around the wrong way. The problem arises when you act on the link.. I've ammended the answer..... *@hced:* I have no idea about Darwin specifically, but here is a link to *[moreutils](http://kitenet.net/~joey/code/moreutils/)*... – Peter.O Oct 20 '11 at 17:28
  • 1
    @hced If you have MacPorts, then you can use `port install moreutils` – Max Nanasy Jul 30 '13 at 21:59
  • homebrew has sponge in the moreutils package as well. brew install moreutils [2.2.3] ==> Downloading https://homebrew.bintray.com/bottles/moreutils-0.57.el_capitan.bottle.1.tar.gz – Jesse Sanford Nov 24 '15 at 03:43
2

Similar to the accepted answer, however if you are coming here because you want to prepend to the first line - rather than prepend an entirely new line - then use this command.

sed -i "1 s/^/string_replacement/" some_file

The -i flag will do a replacement within the file (rather than creating a new file).
Then the 1 will only do the replacement on line 1.

Finally, the s command is used which has the following syntax s/find/replacement/flags.
In our case we don't need any flags. The ^ is called a caret and it is used to represent the very start of a string.

JustCarty
  • 3,839
  • 5
  • 31
  • 51
2

Simplest way IMO would be to use echo and cat:

echo "Prepend" | cat - inputfile > outputfile

Or for your example basically replace the tee -a ~/Documents/Homebrew\ Updates.txt with cat - ~/Documents/Homebrew\ Updates.txt > ~/Documents/Homebrew\ Updates.txt

Edit: As stated by hasturkun this won't work, try:

echo "Prepend" | cat - file | tee file

But this isn't the most efficient way of doing it any more...

J V
  • 11,402
  • 10
  • 52
  • 72
  • 1
    This will not work. It will begin by truncating the file, then trying to read from it. – Hasturkun Oct 18 '11 at 12:39
  • I've used it before (The good old `cat image.jpg secret.zip > image.jpg` trick) Edit: Whoops, must have used a different one, yeah scratch this answer. – J V Oct 18 '11 at 12:40
  • 1
    Try it, the shell first opens the file for writing, truncating it, then runs cat (which on my system causes cat to complain that the input file is the output file), causing the file to only contain the contents of the second file – Hasturkun Oct 18 '11 at 12:44
  • This works. Test it yourself on your own. Some people like to comment without trying for themselves. – mimoralea Jul 22 '14 at 16:03
1

Try this http://www.unix.com/shell-programming-scripting/42200-add-text-beginning-file.html There is no direct operator or command AFAIK.You use echo, cat, and mv to get the effect.

SidJ
  • 669
  • 12
  • 29
  • You may want to include at least some of the information behind that link, you may want to see [answer] and http://meta.stackexchange.com/q/8231/20270 – Hasturkun Oct 18 '11 at 12:34
1
{ date; brew update |tee /dev/tty; cat updates.txt; } >updates.txt.new
mv updates.txt.new updates.txt

I've no idea why you want to do this. It's pretty standard that logs like this have later entries appearing, well, later in the file.

Mark Edgar
  • 4,707
  • 2
  • 24
  • 18