8

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?

1
  • 2
    I would replace the first sed invocation with a grep. Sep 9 at 8:26
13

Checked at GNU Sed:

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

The flags must go in that order -ni.

Explanation: 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.

4
  • 1
    @nezabudka - that is beautiful. How do I reverse the match though. I actually need the reverse action: when line does not match zz then delete it from file1, and print to file2. Sep 9 at 9:10
  • @400theCat, remove the exclamation mark !
    – nezabudka
    Sep 9 at 9:36
  • last thing: I need to append to file2, not overwrite. Is it possible ? Sep 9 at 9:40
  • @Quasímodo - no, it does not have to be sed. I accepted @nezabudka because I like the simplicity of his sed solution. But please do provide your awk solution, if you have an idea. Sep 9 at 10:54
8
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).

1
  • 4
    I love this. Blindingly obvious in retrospect, but I had never thought of using select() with -p like this before. I'm going to be stealing this! >:)
    – terdon
    Sep 9 at 21:24
5

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?

3
  • how can I reverse the match? ie, if line matches zz, it should stay in file1. if not match, it should go to file2. Sep 9 at 11:20
  • how can I use variable instead of "file2"? The following does not work because $F is inside single quotes: gawk -i inplace '!/^: 1:0;/{print >> "$F"; next} 1' file1. But when I change the single quotes to double quotes, the gawk construct does not work anymore. Thank you. Sep 10 at 4:09
  • 1
    @400theCat unlike some other tools, there's no apparently magical incantations in awk scripts, it's just plain Algol-like syntax just like C, Ada, pascal, etc. etc. and you just write it to do what you want it to do. I'm sure you can understand the existing scripts and at least try to tweak the logic to do whatever else you want. Try it, you'll probably get it right almost immediately. For using a variable, see stackoverflow.com/questions/19075671/….
    – Ed Morton
    Sep 12 at 3:57
4

Another way, using ed:

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

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

1
  • Note W is not POSIX, in contrast to w. If you use Ex instead, w!>>file2 is POSIX.
    – Quasímodo
    Sep 12 at 13:10
3

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
asdasd
baba zzbabab
asdasd
sadkzzpasdad

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

$ cat file2 
baba zzbabab
sadkzzpasdad

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.
4
  • 1
    @guest_7 I am used to writing print STDOUT "foo\n". Is there any benefit/advantage in using print(STDOUT),"foo" or is it just classic perl TIMTOWTDI? Just curious, and you seem to know your Perl.
    – terdon
    Sep 10 at 15:08
  • 1
    print(STDOUT),"foo\n" are two statements. First is print printing onto the stdout the default var $_. Second is the string "foo\n" just there. It has NO relation to the print.
    – guest_7
    Sep 10 at 15:15
  • 1
    Ah! Of course! I see now, the comma allows you to use the trailing if on both statements, like doing if(/zz/){print; s///}, Gotcha. But is there any difference between print STDOUT and print(STDOUT) or is it just a question of stylistic preference?
    – terdon
    Sep 10 at 15:22
  • 2
    In this specific case the brackets are necessary lest print will think STDOUT is the first argument to be printed and s/.*//s is the second argument (it's return value in a list context actually) to be printed. With brackets it implies that $_ ( current line) is printed on stdout and the s/.*//s is there to null the current line so that when it gets printed in the file1 ir should Not show up, essentially deleted.
    – guest_7
    Sep 10 at 16:52
1

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
-1
awk '/zz/' file1 >file2 && awk '!/zz/' file1 >file3 && mv file3 file1
1
  • 1
    Any time you find yourself calling multiple awk scripts on the same input you can be sure you have the wrong approach.
    – Ed Morton
    Sep 12 at 3:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.