How do I add text to the beginning of a file in Bash?
Hi I want to prepend text to a file. For example I want to add tasks to the beginning of a todo.txt file. I am aware of echo ‘task goes here’ >> todo.txt but that adds the line to the end of the file (not what I want).
11 Answers 11
echo 'task goes here' | cat - todo.txt > temp && mv temp todo.txt
sed -i '1s/^/task goes here\n/' todo.txt
sed -i '1itask goes here' todo.txt
sed -i '.bak' '1s/^/task goes here\'$'\n/g' todo.txt
echo -e "task goes here\n$(cat todo.txt)" > todo.txt
echo 'task goes here' | cat - todo.txt > temp && mv temp todo.txt
the first one works great! would you mind explaining the logic? im not particularly sure how to interpret the syntax.
@user8347: Pipe ( | ) the message ( echo ‘. ‘ ) to cat which uses — (standard input) as the first file and todo.txt as the second. cat conCATenates multiple files. Send the output ( > ) to a file named temp . If there are no errors ( && ) from cat then rename ( mv ) the temp file back to the original file ( todo.txt ).
@itaifrenkel: I’d have to see what you did, but if cat receives a literal backslash n, it won’t convert it to a newline. Something else must have done that. Instead of cat , try piping into hexdump -C to see if you’re actually sending backslash and n or if it’s a newline. You could also try cat -e to show line endings.
@Kira: The 1 means do the next command only on line one of the file and the i command is insert. Look in the man page under the «Addresses» section and in the «Zero- or One- address commands» section.
A simpler option in my opinion is :
echo -e "task goes here\n$(cat todo.txt)" > todo.txt
This works because the command inside of $(. ) is executed before todo.txt is overwritten with > todo.txt
While the other answers work fine, I find this much easier to remember because I use echo and cat every day.
EDIT: This solution is a very bad idea if there are any backslashes in todo.txt , because thanks to the -e flag echo will interpret them. Another, far easier way to get newlines into the preface string is.
echo "task goes here $(cat todo.txt)" > todo.txt
. simply to use newlines. Sure, it isn’t a one-liner anymore, but realistically it wasn’t a one-liner before, either. If you’re doing this inside a script, and are worried about indenting (e.g. you’re executing this inside a function) there are a few workarounds to make this still fit nicely, including but not limited to:
echo 'task goes here'$'\n'"$(cat todo.txt)" > todo.txt
Also, if you care about whether a newline gets added to the end of todo.txt , don’t use these. Well, except the second-to-last one. That doesn’t mess with the end.
printf would be a lot more consistent across platforms and should generally work more smoothly than echo -e
The moreutils have a nice tool called sponge :
echo "task goes here" | cat - todo.txt | sponge todo.txt
It’ll «soak up» STDIN and then write to the file, which means you don’t have to worry about temporary files and moving them around.
You can get moreutils with many Linux distros, through apt-get install moreutils , or on OS X using Homebrew, with brew install moreutils .
I would use tee instead of sponge which come by default on most distro echo «task goes here» | cat — todo.txt | tee todo.txt
@stevenpenny Good point. Now I remember why I didn’t suggest it originally. I came back to this answer not noticing that the output file name was the same as the input, which is why you need sponge to buffer it all before writing it back out.
You can use the POSIX tool ex :
You can create a new, temporary file.
echo "new task" > new_todo.txt cat todo.txt >> new_todo.txt rm todo.txt mv new_todo.txt todo.txt
You might also use sed or awk . But basically the same thing happens.
Say you’re out of disk space so that new_todo.txt gets written only partially. Your solution appears to lose the original file.
@Keith Someone working on a VM who didn’t expect to need a particularly large virtual drive. Or someone moving a large file. In any case, the real argument against this is directory permissions; if you don’t have permission to create new files in the given directory, the only command that will successfully execute in your script is the rm of the original file.
If the text file is small enough to fit in memory, you don’t have to create a temporary file to replace it with. You can load it all into memory and write it back out to the file.
echo "$(echo 'task goes here' | cat - todo.txt)" > todo.txt
It’s impossible to add lines to the beginning of the file without over writing the whole file.
As far as I’m aware, it’s only limited by the amount of memory available. I’ve filled up variables well over 100MB into memory. text=$(cat file) . Be careful to only use text though, because shell variables aren’t binary clean mywiki.wooledge.org/BashFAQ/058
You cannot insert content at the beginning of a file. The only thing you can do is either replace existing content or append bytes after the current end of file.
Any solution to your question then requires a temporary file (or buffer) to be created (on memory or on disk) which will eventually overwrite the original file.
Beware of not losing data by preserving the original file while creating the new one, should the file system happen to be full during the process. eg:
cat <(echo task go there) todo.txt >todo.txt.new && mv todo.txt.new todo.txt
Downvoters are welcome to explain their motivation. None of the remaining answers, including the accepted one, do contradict anything in my reply.
This is difficult to parse as the < . >look like brackets, which I assume they are not. A space between the < and the ( might help?
This is not working for me. echo HOME=\»/g/Users/timregan/\» | cat — ‘F:\Program Files\Git\etc\profile’ works but cat
@dumbledad You are overthinking my reply. There is nothing for you to parse in it. A space between the < and the ( would break the syntax. Try cat <(echo HOME=\"/g/Users/timregan/\") 'F:\Program Files\Git\etc\profile'
echo 'task goes here' | cat - todo.txt | tee todo.txt
Final answer
I wasn’t too satisfied with the answers as they felt like too much typing. I liked John Alberts his answer but couldn’t stand to type -e . Unfortunately, I accidentally read over John Alberts his echo 2 liner as well (significantly reducing the value of this answer and me 30 minutes playing around, but oh well, it happens).
In any case, I was focused on finding something that meant you only needed to type the filename and text you want to prepend.
Moreover, I was searching for something that looked aesthetically intuitive. With that I mean: the preprend needs to physically show, even if it’d be an illusion it’d have the effect of a mnemonic.
So I tried an approach with herestrings since in the right context they reduce cognitive strain (i.e. typing < 3 times doesn't require too much thinking power).
I created a file test.txt with the word «monkeys» .
You need to manually press enter yourself.
On the second line the > is from the shell itself, you don’t need to type that.
(1) What I couldn’t manage was a one liner. There seems to be no herestring combination in which I could use $() and \n . Which is why you need to press the newline manually yourself.
The command substitution $(cat file) can be replaced by the equivalent but faster $( < file) .
More typing, but I admit a bit less cognitive strain since cat is being typed twice and is more well-known than the trick of the Bash Reference Manual.
How to insert text after a certain string in a file?
please edit you Q and show the input and the output lines. Because you Q is unclear. You could also do echo «Hello World [option]» >> file.txt , but it doesn’t make sense.
I edited the question to provide more information for reference but the accepted answer was what I was trying to accomplish
If you just want to edit a config file this is the best solution I found: unix.stackexchange.com/a/78076/20661
4 Answers 4
Append line after match
Insert line before match
Additionally you can take backup and edit input file in-place using -i.bkp option to sed
The above code will append/insert the line for every single match. If you want to append/insert the line for the first match only, you can prepend 0, to the commands: sed ‘0,/\[option\]/a Hello World’ input or sed ‘0,/\[option\]/i Hello World’ input
If the appended string is a multiline text u can save it to a file (e.g.: snippet.txt) and inject this file after the pattern using: sed -i ‘/pattern/ r snippet.txt’ filename
Yes, it is possible with sed :
sed '/pattern/a some text here' filename
$ cat test foo bar option baz $ sed '/option/a insert text here' test foo bar option insert text here baz $
Keep in mind that some characters can not be included literally so one has to use escape sequences (they begin with a backslash) e.g. to print a literal backslash one has to write \\ .
It’s actually the same with sed but in addition each embedded newline in the text has to be preceded by a backslash:
sed '/PATTERN/a\ add one line\ \\and one more' infile
For more details on escape sequences consult the manual.
Also, to address some of the comments: the above commands DO NOT edit the file in place, they just print the result to the standard output. To actually modify the input file you would either use the -i switch if your awk / sed support it (consult the manual) or redirect to a temporary file then overwrite the original e.g.
cmd infile > outfile mv outfile infile
Or use ed / ex which can edit the files in-place on all platforms:
Remember: with ed / sed / ex , a appends and i inserts; with awk , to insert, move the 1 to the end.
In Bash, how do I add a string after each line in a file?
How do I add a string after each line in a file using bash? Can it be done using the sed command, if so how?
7 Answers 7
If your sed allows in place editing via the -i parameter:
sed -e 's/$/string after each line/' -i filename
If not, you have to make a temporary file:
typeset TMP_FILE=$( mktemp ) touch "$" cp -p filename "$" sed -e 's/$/string after each line/' "$" > filename
Why do you touch the temporary file? And I would make the sed command conditional on the success of the cp or the original could be lost.
I touched the file to quickly reserve the temporary name. This may not be needed. I think you are right about making the sed command conditional on the success of the cp. Why not edit the code to make that fix. I won’t mind a bit! Yours, Tom
@Oxwivi: sed -e ‘s/^/string after each line/’ -i filename $ means end-of-line. ^ means beginning of line.
Could you please explain what the $ sign does in your answer? Answers are more helpful if you understand what is happening.
@shuhalo According to gnu sed documentation $ also acts as a special character only at the end of the regular expression . So $ regexp grabs the end of each line and s/$/appendix/ substitues the end of a line with a given string. This is my understanding.
I prefer echo . using pure bash:
cat file | while read line; do echo $$string; done
This is a nice solution, it can be used with the output of ls (or anything that produces multiline output).
I prefer using awk . If there is only one column, use $0 , else replace it with the last column.
awk '$0=$0"string to append after each line"' file > new_file
Also, to append line numbers (as was in my case), one can use NR variable: awk ‘
@AdilSaju sed ‘s/ //g’ this worked for my requirement, but it will remove space in every line, thats what option g for and everywhere in a line.
@AdilSaju awk Remove , like
If you have it, the lam (laminate) utility can do it, for example:
$ lam filename -s "string after each line"
suffix=foobar while read l ; do printf '%s\n' "$l" "$" ; done < file | sponge file
suffix=foobar xargs -L 1 printf "%s$\n" < file | sponge file
suffix=foobar join file file -e "$" -o 1.1,2.99999 | sponge file
suffix=foobar paste file <(yes "$" | head -$(wc -l < file) ) | sponge file
Of course sponge can be replaced with a temp file, afterwards mv 'd over the original filename, as with some other answers.
This is just to add on using the echo command to add a string at the end of each line in a file:
cat input-file | while read line; do echo $"string to add" >> output-file; done
Adding >> directs the changes we've made to the output file.
Sed is a little ugly, you could do it elegantly like so:
hendry@i7 tmp$ cat foo bar candy car hendry@i7 tmp$ for i in `cat foo`; do echo $bar; done barbar candybar carbar
Fails for files with more lines than the shell's maximum argument limit. Try: cat foo | while read a ; do echo $bar ; done or something like that instead; it's a suitable replacement for for in in most cases.
Yes it does: $ cat foo foo bar baz alex@armitage:~$ for i in cat foo ; do echo $bar; done foobar barbar bazbar but after some tests, I might be wrong about my reasoning, but Dennis is right
@hendry Yes, this does fail for lines with spaces in them, such as echo "foo bar" | (for i in `cat`; do echo $bar; done) which prints 2 lines even though input is 1 line foo bar (and cat reading from stdin instead of a file makes no difference for this).