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


    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 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