I'm writing shell scripts for my server, which is a shared hosting running FreeBSD. I also want to be able to test them locally, on my PC running Linux. Hence, I'm trying to write them in a portable way, but with sed I see no way to do that.

Part of my website uses generated static HTML files, and this sed line inserts correct DOCTYPE after each regeneration:

sed -i '1s/^/<!DOCTYPE html> \n/' ${file_name.html}

It works with GNU sed on Linux, but FreeBSD sed expects the first argument after -i option to be extension for backup copy. This is how it would look like:

sed -i '' '1s/^/<!DOCTYPE html> \n/' ${file_name.html}

However, GNU sed in turn expects the expression to follow immediately after -i. (It also requires fixes with newline handling, but that's already answered in here)

Of course I can include this change in my server copy of the script, but that would mess i.e. my use of VCS for versioning. Is there a way to achieve this with sed in a fully portable way?

  • The two sed snippets you provided are identical, are you sure there isn't a typo? Also, i am able to execute GNU sed supplying the backup extension right after -i
    – iruvar
    Commented Sep 29, 2013 at 23:34
  • Duh, thanks for spotting this. I've fixed my question. The second line results in error in my sed, it expects '1s/^/<!DOCTYPE html> \n/' to be a file and complains it can't find it.
    – Red
    Commented Sep 29, 2013 at 23:37
  • 2
    Cross reference: sed in-place flag that works both on Mac (BSD) and Linux on Stack Overflow.
    – MvG
    Commented Jan 10, 2017 at 8:50

7 Answers 7


GNU sed accepts an optional extension after -i. The extension must be in the same argument with no intervening space. This syntax also works on FreeBSD sed.

sed -i.bak -e '…' SOMEFILE

Note that on FreeBSD, -i also changes the behavior when there are multiple input files: they are processed independently (so e.g. $ matches the last line of each file). Also this won't work on BusyBox.

If you don't want to use backup files, you could check which version of sed is available.

# Assume that sed is either FreeBSD/macOS or GNU
case $(sed --help 2>&1) in
  *GNU*) set sed -i;;
  *) set sed -i '';;
"$@" -e '…' "$file"

Or alternatively, to avoid clobbering the positional parameters, define a function.

case $(sed --help 2>&1) in
  *GNU*) sed_i () { sed -i "$@"; };;
  *) sed_i () { sed -i '' "$@"; };;
sed_i -e '…' "$file"

If you don't want to bother, use Perl.

perl -i -pe '…' "$file"

If you want to write a portable script, don't use -i — it isn't in POSIX. Do manually what sed does under the hood — it's only one more line of code.

sed -e '…' "$file" >"$file.new"
mv -- "$file.new" "$file"
  • 3
    GNU sed -i also implies -s. And the easiest way to check for a GNU sed is with the sed v command which is a valid noop for GNU but fails everywhere else.
    – mikeserv
    Commented Sep 4, 2014 at 19:11
  • Inspired by the above tips, here's an single-line (if ugly) portable version for those who really want one, though it does spawn a subshell: sed -i$(sed v < /dev/null 2> /dev/null || echo -n " ''") -e '...' "$file" If it's not GNU sed, it inserts a space followed by two singe quotes after -i so that it works on BSD. GNU sed gets only -i.
    – Ivan X
    Commented Apr 26, 2015 at 21:42
  • 2
    @IvanX I'm wary of using the presence of the v command to test for GNU sed. What if FreeBSD decided to implement it? Commented Apr 26, 2015 at 21:54
  • @Gilles Fair point, but the man page for GNU sed describes v as being exactly for that purpose (detecting that it's GNU sed and not something else), so one would hope that *BSD would honor that. I can't think of another test, offhand, that takes no action on GNU sed, while causing an error on BSD sed (or vice versa), other than using -i itself, but that would require creating a dummy file first. Your test for sed above is OK but unwieldy for inline. Avoiding -i entirely, as you suggest, certainly seems like the safest bet, but I'm ok with using sed v given that's its purpose for existing.
    – Ivan X
    Commented Apr 26, 2015 at 22:20
  • 1
    @lapo An alternative is to define a function, see my edit. Commented Mar 22, 2016 at 12:04

If you don't find a trick to make sed play nice, you could try:

  1. Don't use -i :

    sed '1s/^/<!DOCTYPE html> \n/' "${file_name.html}" > "${file_name.html}.tmp" &&
      mv "${file_name.html}.tmp" "${file_name.html}"
  2. Use Perl

    perl -i -pe 'print "<!DOCTYPE html> \n" if $.==1;' "${file_name.html}"


You can always use ed to prepend a line to an existing file.

$ printf '0a\n<!DOCTYPE html>\n.\nw\n' | ed my.html


The bits around the <!DOCTYPE html> are commands to ed instructing it to add that line to the file my.html.


I believe this command in sed can also be used:

$ sed -i '1i<!DOCTYPE html>\n` testfile.csv
  • I eventually resorted to Perl, but using ed is a good alternative that's not popular among Unix-like users as it should be.
    – Red
    Commented Sep 30, 2013 at 14:29
  • @Red - glad to hear you resolved you issue. Yeah I'd not seen that one before, googling turned it up and it actually seemed like the most portable, apt way to do this.
    – slm
    Commented Sep 30, 2013 at 14:30

You can also do manually what perl -i used to do under the hood:

{ rm -f file && { echo '<!DOCTYPE html>'; cat; } > file;} < file

Like perl -i, there's no backup, and like most solutions given here, beware it may affect the permissions, ownership of the file and may turn a symlink into a regular file.


sed '1i\
<!DOCTYPE html>' file 1<> file

sed would overwrite the file over itself, so would not affect ownership and permissions or symlinks. It works with GNU sed because sed will typically have read a buffer full of data from file (4k in my case) before overwriting it with the i command. That wouldn't work if the file was more than 4k except for the fact that sed also buffers its output.

Basically sed works on blocks of 4k for reading and writing. If the line to insert is smaller than 4k, sed will never overwrite a block it has not read yet.

I wouldn't count on it though.

Beware that with all those solutions, if the file system is full or the system crashes in the middle, you may end up losing data.

  • Should be echo '<!DOCTYPE html>' or escaped without "" quotes.
    – A.D.
    Commented May 22, 2015 at 12:36
  • @A.D. Good point. I tend to forget about that bug^Wfeature of interactive bash/zsh as I generally disable it for myself. Commented May 22, 2015 at 12:41
  • This is one of the very few answers here that doesn't have a wide open security hole of redirecting to a "temp file" with a static, predictable name without checking if it already exists. I believe this should be the accepted answer. Very nice demonstration of the use of group commands, also.
    – Wildcard
    Commented Mar 11, 2016 at 1:59
  • I really like this. As to losing permissions (which perl -i preserves), one could stat -c %a before the rm and then chmod before the close brace. But not only are you then getting into ugly code territory, you'd also need to consider that a setgid bit on the original file may not be desirable on the new one since the group may have changed.
    – laubster
    Commented May 3, 2021 at 15:56
  • If the system crashes (or the command gets prematurely terminated) while you’re running sed -i, do you totally lose your file? (It’s my understanding that it writes output to a new (temporary) file, and then deletes the input file and renames the output file, as simulated in terdon’s answer.) The above command is vulnerable to interruption, unless I’m overlooking something. Commented Sep 1, 2022 at 21:55

FreeBSD sed, which is used on Mac OS X as well, needs the -e option after the -i switch to define & recognise the following (regex) command correctly & unambiguously.

In other words, sed -i -e ... should work with both FreeBSD & GNU sed.

More generally, omitting the backup extension after FreeBSD sed -i requires some explicit sed option or switch following the -i to avoid confusion on part of FreeBSD sed while parsing its command-line arguments.

(Note, however, that sed in-place file edits lead to file inode changes, see "In-place" editing of files).

(As a general hint, recent versions of FreeBSD sed have the -r switch to increase compatibility with GNU sed).

echo a > testfile.txt
ls -li testfile.txt
#gsed -i -e 's/a/A/' testfile.txt
#bsdsed -i 's/a/A/' testfile.txt  # does not work
bsdsed -i -e 's/a/A/' testfile.txt
ls -li testfile.txt
cat testfile.txt
  • 9
    No, bsdsed -i -e 's/a/A/' is not in-place editing, it's editing with saving the original with a "-e" suffix (testfile.txt-e). Commented May 22, 2015 at 11:27
  • 1
    note that GNU sed also supports -E (in addition to -r) for compatibility with FreeBSD. -E is likely to be specified in the next POSIX version, so we should all be forgetting about that -r non-sense and pretend it never existed. Commented May 22, 2015 at 11:30

You can use Vim in Ex mode:

ex -s -c '1i|<!DOCTYPE html>' -c x file
  1. 1 select first line

  2. i insert text and newline

  3. x save and close

  • There's nothing Vim-specific here. This is fully compliant with POSIX specifications for ex, except that implementations are not required to support multiple -c flags. For definite portability I would use printf '%s\n' 1i '<!DOCTYPE html>' . x | ex file
    – Wildcard
    Commented Oct 16, 2016 at 8:31

To emulate sed -i for a single file portably while avoiding race conditions as much as possible:

sed 'script' <<FILE >file
$(cat file)

By the way, this also handles the possible problem that sed -i introduces in that, depending on directory and file permissions, sed -i might enable a user to overwrite a file which that user does not have permissions to edit.

You might also do backups like:

sed 'script' <<FILE >file
$(tee file.old <file)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .