Delete lines matching pattern in file1 and save these deleted lines to file2

All we need is an easy explanation of the problem, so here it is.

I have file1, and I need to delete lines matching a pattern. But I would like to save these deleted lines in another file2.

sed    '/zz/!d' file1 > file2
sed -i '/zz/d' file1

Is there a way to combine these commands into one ?

Or is there a more elegant way to do it?

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

Checked at GNU Sed:

sed -ni '/zz/!{p;b};w file2' file1

The flags must go in that order -ni.

We do not stop the script with the d command, but set the -n flag (silent) and write lines that do not match the template using the p command (Print the current pattern space) and jump with b to the end of the script. Lines matching the pattern reach the w command, which writes the pattern space to the file.

Method 2

perl -pi -e 'select( /zz/ ? STDOUT : ARGVOUT )' file1 > file2

-i handles in-place editing of file1. -p prints lines after running the perl program. All the program needs to do is select where the output goes. In this case, that be achieved using ?: to choose either the standard output, or ARGVOUT (which is what -i uses).

Method 3

With any Awk:

awk '/zz/{print >> "file2"; next} 1' file1 > tmp && mv tmp file1

If you don’t like explicitly creating a temporary file, with GNU Awk:

gawk -i inplace '/zz/{print >> "file2"; next} 1' file1

Change >> to > if you want to truncate file2 instead of appending to it.

See also: Why does "1" in awk print the current line?

Method 4

Another way, using ed:

# Delete file2 if it already exists first.
ed -s file1 <<'EOF'
g/zz/.W file2\

Ever line matching the basic regular expression zz is first appended to file2, and then deleted. Finally the modified file1 is saved.

Method 5

Just use perl with the -i flag instead and have it print matching lines to stdout so you can redirect them to a file:

$ cat file1
baba zzbabab

$ perl -i -ne 'if(/zz/){ print STDOUT } else{ print }' file1 > file2

You could rewrite the above via a string form of eval where we construct
 the above piece of perl code based on presence of zz:

$ perl -i -ne 'eval "print ".qw[STDOUT][!/zz/]' file1 > file2

$ cat file1

$ cat file2 
baba zzbabab

Another way in perl can be:

$ perl -pi -e 'print(STDOUT),s/.*//s if /zz/' file1 > file2
  • use the autoprint mode -p
  • print into stdout the zz lines then null them when they will be written back onto file1 via the -i mode.

Method 6

Here’s a silly pipe oriented method using GNU grep, along with the sponge util for convenience:

grep 'zz'      file1 | tee  file2 | 
grep -vf -     file1 | sponge file1

Or without tee, and one less pipe:

grep 'zz'      file1 > file2 
grep -vf file2 file1 | sponge file1

Method 7

awk '/zz/' file1 >file2 && awk '!/zz/' file1 >file3 && mv file3 file1

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from or, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply