On GNU/Linux: sed -i '1s/^/new first line\n/' file.txt. On macOS or any BSD, the same command fails until you add an explicit empty suffix: sed -i '' '1s/^/new first line\n/' file.txt. Neither one does anything at all to an empty file, and neither is safe on a symlink or a hard-linked file. The form that behaves identically in all four of those cases is ed:
printf '0a\nnew first line\n.\nw\n' | ed -s file.txtBelow is what each method actually did on this machine, measured rather than described — including two commands the previous version of this page recommended that either destroy file permissions or do not run.
Table of Contents
The -i suffix argument is the real portability problem
The previous version gave sed -i '1s/^/…/' filename with no qualification and said the commands “work on all Linux distributions”. BSD sed reads the word after -i as the backup extension, so it takes your script as the suffix and your filename as the script:
$ sed -i '1s/^/[INFO] Log Entry - $(date)\n/' t1.txt
sed: 2: "t1.txt
": undefined label '1.txt'
rc=1The file is untouched and no backup is written. GNU sed documents the opposite convention — the suffix is attached to the option, not passed as a separate word: “-i[SUFFIX], –in-place[=SUFFIX] … edit files in place (makes backup if SUFFIX supplied)”, so there it is -i for no backup and -i.bak for one. There is no single spelling that satisfies both; either add the '' and accept the command is macOS/BSD-only, or use ed. (I have no Linux machine here, so the GNU half of that sentence is quoted from GNU sed(1), not run.)
Two smaller corrections while I am here. The old page’s replacement string was wrapped in single quotes, so $(date) is inserted literally, not expanded — you get the seven characters $(date) in your log header. And an unescaped & in a sed replacement means “the text that matched”, which here is the empty string:
$ sed -i '' '1s/^/Tom & Jerry\n/' f3.txt
Tom Jerry
$ sed -i '' '1s/^/Tom \& Jerry\n/' f2.txt
Tom & JerrySilent data loss in a header line. cat and ed have no such problem because neither treats the text as a replacement expression.
An empty file defeats sed completely
sed is line-oriented and an empty file has no line 1, so the 1s/// address never matches. It exits 0 and reports nothing. Same input, five methods, byte counts after:
method size after
sed -i '' '1s/^/HDR\n/' 0 <-- did nothing, rc=0
printf | cat - f > t 4
awk 'BEGIN{print}{print}' 4
ed -s (0a … . w) 4If you prepend headers to log files that may not exist yet, this is the bug you will ship. The other edge case runs the other way: on a 19-byte file whose last line has no trailing newline, prepending a 4-byte header gives 23 bytes with sed and with cat, and 24 bytes with awk — awk silently adds the missing newline. Both are defensible; only one of them is what you asked for.
The old page’s cat command makes a 0600 file world-readable
This is the correction worth leading with if you keep anything private in the file you are editing. The recommended command was echo "…" | cat - filename > temp && mv temp filename. mv installs a new file created under your umask, so the original mode is gone. Measured on files that all started at mode 600:
inode mode
before sed 381591304 600
after sed 381591309 600
before cat+mv 381591306 600
after cat+mv 381591312 644 <-- permissions lost
before awk+mv 381591308 600
after awk+mv 381591314 644 <-- permissions lost
before ed 381591305 600
after ed 381591305 600 <-- same inodeRun that against ~/.ssh/config, a .env, or a credentials file and you have just published it to every account on the box. The fix costs one word — write back through the existing file instead of replacing it, which keeps both the inode and the mode:
printf '%s\n' 'HDR' | cat - file.txt > file.tmp && cat file.tmp > file.txt && rm file.tmpThe same distinction decides what happens to links. cat - link > tmp && mv tmp link replaced the symlink with an ordinary file and left the target unmodified; the cat file.tmp > file.txt form followed the link and updated the target, which is almost certainly what you meant. BSD sed -i refuses outright — sed: s/link.txt: in-place editing only works for regular files — while GNU sed documents needing --follow-symlinks to do it. And on a hard-linked file, sed -i quietly breaks the link:
before: links=2 inode one=381591319 two=381591319
after : links=1 inode one=381591322 two=381591319
one.txt=[HDR|body|] two.txt=[body|]So the old page’s “sed -i and ed are best for modifying files in place” is half right. ed edits in place. sed -i writes a new file and renames it over the old one.
The awk example does not run
Verbatim from the previous version, on this machine:
$ awk 'BEGIN {print "Timestamp: " strftime("%Y-%m-%d %H:%M:%S")} {print}' d.txt > temp && mv temp d.txt
awk: calling undefined function strftime
source line number 1
rc=2strftime is a gawk function — it appears in the GNU awk manual’s “Time Functions”, and the awk on this machine (awk version 20200816, the one-true-awk that ships with macOS) does not define it. I have not tested mawk. Pass the timestamp in from the shell instead and the dependency disappears:
awk -v ts="$(date '+%Y-%m-%d %H:%M:%S')" 'BEGIN{print "Timestamp: " ts} {print}' d.txtFor appending rather than prepending, see appending strings to a file in bash and appending to a file with cat. The script that reproduces every block above builds its own scratch files, prints one line per claim, and cleans up after itself.
