Search code examples
sedgnu-sed

Why does "sed -n -i" delete existing file contents?


Running Fedora 25 server edition. sed --version gives me sed (GNU sed) 4.2.2 along with the usual copyright and contact info. I've create a text file sudo vi ./potential_sed_bug. Vi shows the contents of this file (with :set list enabled) as:

don't$
delete$
me$
please$

I then run the following command:

sudo sed -n -i.bak /please/a\testing ./potential_sed_bug

Before we discuss the results; here is what the sed man page says:

-n, --quiet, --silent suppress automatic printing of pattern space

and

-i[SUFFIX], --in-place[=SUFFIX] edit files in place (makes backup if extension supplied). The default operation mode is to break symbolic and hard links. This can be changed with --follow-symlinks and --copy.

I've also looked other sed command references to learn how to append with sed. Based on my understanding from the research I've done; the resulting file content should be:

don't
delete
me
please
testing

However, running sudo cat ./potential_sed_bug gives me the following output:

testing

In light of this discrepancy, is my understanding of the command I ran incorrect or is there a bug with sed/the environment?


Solution

  • tl;dr

    • Don't use -n with -i: unless you use explicit output commands in your sed script, nothing will be written to your file.

    • Using -i produces no stdout (terminal) output, so there's nothing extra you need to do to make your command quiet.


    By default, sed automatically prints the (possibly modified) input lines to whatever its output target is, whether implied or explicitly specified: by default, to stdout (the terminal, unless redirected); with -i, to a temporary file that ultimately replaces the input file.

    In both cases, -n suppresses this automatic printing, so that - unless you use explicit output functions such as p or, in your case, a - nothing gets printed to stdout / written to the temporary file.

    • Note that the automatic printing applies to the so-called pattern space, which is where the (possibly modified) input is held; explicit output functions such as p, a, i and c do not print to the pattern space (for potential subsequent modification), they print directly to the target stream / file, which is why a\testing was able to produce output, despite the use of -n.

    Note that with -i, sed's implicit printing / explicit output commands only print to the temporary file, and not also to stdout, so a command using -i is invariably quiet with respect to stdout (terminal) output - there's nothing extra you need to do.


    To give a concrete example (GNU sed syntax).

    Since the use of -i is incidental to the question, I've omitted it for simplicity. Note that -i prints to a temporary file first, which, on completion, replaces the original. This comes with pitfalls, notably the potential destruction of symlinks; see the lower half of this answer of mine.

    # Print input (by default), and append literal 'testing' after 
    # lines that contain 'please'.
    $ sed '/please/ a testing' <<<$'yes\nplease\nmore'
    yes
    please
    testing
    more
    
    # Adding `-n` suppresses the default printing, so only `testing` is printed.
    # Note that the sequence of processing is exactly the same as without `-n`:
    # If and when a line with 'please' is found, 'testing' is appended *at that time*.
    $ sed -n '/please/ a testing' <<<$'yes\nplease\nmore'
    testing
    
    # Adding an unconditional `p` (print) call undoes the effect of `-n`.
    $ sed -n 'p; /please/ a testing' <<<$'yes\nplease\nmore'
    yes
    please
    testing
    more