jkshah
jkshah

Reputation: 11703

Move lines matching a pattern from one file to another

I want to move lines matching certain pattern from file1 to file2. Analogous to operation cut and paste from one file to another in windows

Example

let's say I want to cut all lines containg bar from file1 and paste it into newly created file2

Input:

file1

bla foo bla
bla bar bla
bla aaa bla
bla bar bla
bla foo bla

Desired output after processing:

file1

bla foo bla
bla aaa bla
bla foo bla

file2

bla bar bla
bla bar bla

What I have tried

grep creates desired file2 but doesn't modify file1

grep 'bar' file1 > file2

sed -i modifies desired file1 but doesn't create file2

sed -i '/bar/d' file1

If I execute both commands one after another, I get desired result. But here I am looking for a single line command out of curiosity and to make a script more concise.

Your help would be appreciated.

Upvotes: 22

Views: 23282

Answers (5)

potong
potong

Reputation: 58391

This might work for you (GNU sed):

sed -i -e '/bar/{w file2' -e 'd}' file1

An alternative:

sed -i -e '/bar/w file2' -e '//d' file1

To append to file2, write to a temporary file and use cat to append at the end of file in a bash script, or use:

sed -i -e '/bar/w tmpfile' -e '$e cat tmpfile >> file2 && rm tmpfile' -e '//d' file1

N.B. For the last solution, only one input file can be modified at a time.

Upvotes: 19

Sridhar Sarnobat
Sridhar Sarnobat

Reputation: 25236

I like the other answers, but just an (obvious) alternative approach in case you are worried about making a mistake in your pattern and want an easy way to rollback:

grep    'bar' file1 > file2
grep -v 'bar' file1 > file3

Once you're happy with your results (hint: tee instead of > will allow you to see what's getting written to your files):

mv file3 file1

Generally I prefer perl to do substitution like in the above answer, but when the regex idiom gets too complicated and you are semi-blindly copying and pasting commands you don't understand, you're not in control of your precious data.

Upvotes: 0

Chris Seymour
Chris Seymour

Reputation: 85785

This awk script will do the trick:

awk '{a[NR]=$0}END{for(i=1;i<=NR;i++)print a[i] > "file"(a[i]~/bar/?2:1)}' file1

Outputs:

$ cat file1
bla foo bla
bla aaa bla
bla foo bla

$ cat file2
bla bar bla
bla bar bla

Upvotes: 3

Birei
Birei

Reputation: 36262

You can use and select a different filehandle based in a match of a regular expression when printing:

perl -i.bak -ne 'BEGIN { open $oh, q|>|, pop or die } { print { m/bar/ ? $oh : q|ARGVOUT| } $_ }' file1 file2

It yields:

==> file1 <==
bla foo bla
bla aaa bla
bla foo bla

==> file2 <==
bla bar bla
bla bar bla

Upvotes: 6

janos
janos

Reputation: 124646

You can put a && between the two commands to make them a single line. But that won't be more readable, so I don't recommend you do that.

To do the same in one command, you would need something that can edit a file in-place, removing lines you don't want and at the same time printing those lines to stdout or stderr so you could redirect it to the other file. Maybe ed can do this but I don't know how to write that. I don't think there is another "standard" UNIX tool to do this.

Btw, cut & paste actually has 3 steps:

  1. Copy selected text to clipboard
  2. Remove from original file
  3. Paste from clipboard to new file

The 2-step UNIX command does it without a clipboard.

Upvotes: 1

Related Questions