209

I have a text file with the following format. The first line is the "KEY" and the second line is the "VALUE".

KEY 4048:1736 string
3
KEY 0:1772 string
1
KEY 4192:1349 string
1
KEY 7329:2407 string
2
KEY 0:1774 string
1

I need the value in the same line as of the key. So the output should look like this...

KEY 4048:1736 string 3
KEY 0:1772 string 1
KEY 4192:1349 string 1
KEY 7329:2407 string 2
KEY 0:1774 string 1

It will be better if I could use some delimiter like $ or ,:

KEY 4048:1736 string , 3

How do I merge two lines into one?

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
shantanuo
  • 31,689
  • 78
  • 245
  • 403
  • 2
    There is a lot of way for doing this! I've done a [little bench with `pr`, `paste`, `awk`, `xargs`, `sed` and `pure bash`](https://stackoverflow.com/a/47348104/1765658)! (`xargs` is the slower, slower than [tag:bash]!) – F. Hauri - Give Up GitHub Nov 06 '18 at 15:18

21 Answers21

317

paste is good for this job:

paste -d " "  - - < filename
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
  • 10
    a description about the parameters would be a good addition – Pedro Feb 25 '22 at 08:25
  • 9
    This works because `paste` takes always one line from *each input file* and outputs a single line with those lines combined by the separator character defined with `-d`. The trick here is to list magic input file `-` twice. By GNU traditions, filename `-` means read from standard input and listing that magic file twice for `paste` means it will read one line from "first" input file and then one line from "second" input file resulting in total of 2 lines read from standard input. Those lines will then be concatenated with a single space in between. Note that `-d` only takes one byte. – Mikko Rantalainen Apr 06 '22 at 10:33
  • I've made a [comparison between differents merging methods](https://stackoverflow.com/a/47348104/1765658) `paste` is the winner! – F. Hauri - Give Up GitHub Sep 28 '22 at 09:53
224

awk:

awk 'NR%2{printf "%s ",$0;next;}1' yourFile

note, there is an empty line at the end of output.

sed:

sed 'N;s/\n/ /' yourFile
Kent
  • 189,393
  • 32
  • 233
  • 301
  • I've made a [comparison between differents merging methods](https://stackoverflow.com/a/47348104/1765658)! You could consider `pr` and `paste` – F. Hauri - Give Up GitHub Sep 28 '22 at 09:51
  • An explanation what each of the parts in this answer do would be appreciated. I tried using the man pages to understand it, but failed. – lucidbrot May 07 '23 at 11:39
60

Alternative to sed, awk, grep:

xargs -n2 -d'\n'

This is best when you want to join N lines and you only need space delimited output.

My original answer was xargs -n2 which separates on words rather than lines. -d (GNU xargs option) can be used to split the input by any singular character.

nnog
  • 1,607
  • 16
  • 23
35

There are more ways to kill a dog than hanging. [1]

awk '{key=$0; getline; print key ", " $0;}'

Put whatever delimiter you like inside the quotes.


References:

  1. Originally "Plenty of ways to skin the cat", reverted to an older, potentially originating expression that also has nothing to do with pets.
Community
  • 1
  • 1
ghoti
  • 45,319
  • 8
  • 65
  • 104
18

Here is another way with awk:

awk 'ORS=NR%2?FS:RS' file

$ cat file
KEY 4048:1736 string
3
KEY 0:1772 string
1
KEY 4192:1349 string
1
KEY 7329:2407 string
2
KEY 0:1774 string
1

$ awk 'ORS=NR%2?FS:RS' file
KEY 4048:1736 string 3
KEY 0:1772 string 1
KEY 4192:1349 string 1
KEY 7329:2407 string 2
KEY 0:1774 string 1

As indicated by Ed Morton in the comments, it is better to add braces for safety and parens for portability.

awk '{ ORS = (NR%2 ? FS : RS) } 1' file

ORS stands for Output Record Separator. What we are doing here is testing a condition using the NR which stores the line number. If the modulo of NR is a true value (>0) then we set the Output Field Separator to the value of FS (Field Separator) which by default is space, else we assign the value of RS (Record Separator) which is newline.

If you wish to add , as the separator then use the following:

awk '{ ORS = (NR%2 ? "," : RS) } 1' file
Community
  • 1
  • 1
jaypal singh
  • 74,723
  • 23
  • 102
  • 147
  • 1
    Definitely the right approach so +1 but I wonder what the condition is that's being evaluated to invoke the default action of printing the record. Is it that the assignment succeeded? Is it simply `ORS` and that's being treated as `true` since ORS gets a value thats not zero or a null string and awks guessing correctly that it should be a sting instead of numeric comparison? Is it something else? I'm really not sure and so I'd have written it as `awk '{ORS=(NR%2?FS:RS)}1' file`. I parenthesized the ternary expression to ensure portability too. – Ed Morton Aug 21 '14 at 17:27
  • 1
    @EdMorton Yeah, I just saw couple of upvotes on this answer was about to update it to include the braces for safety. Will add parens as well. – jaypal singh Aug 21 '14 at 17:29
  • this solution is superior to others in so many relations. First, you clearly see how many lines are you joining, and can change this to arbitrary N. Second, you can use whatever separator you want, not just a char. Third, you can extend the behavior, adding different separators for specific lines. Fourth, it's straightforward and doesn't make use of some sideways behavior. Thus, it's easy to remember. Fifth, you can combine it with the additional logic, like reverse or trim every other line, if need be. – rudnev Sep 27 '22 at 15:36
18

Here is my solution in bash:

while read line1; do read line2; echo "$line1, $line2"; done < data.txt
Hai Vu
  • 37,849
  • 11
  • 66
  • 93
11

Although it seems the previous solutions would work, if a single anomaly occurs in the document the output would go to pieces. Below is a bit safer.

sed -n '/KEY/{
N
s/\n/ /p
}' somefile.txt
J.D.
  • 1,025
  • 8
  • 14
  • 4
    Why is it safer? What does `/KEY/` do? What does the `p` do at the end? – Stewart May 31 '16 at 11:16
  • the `/KEY/` searches for the line with the `KEY`. the `p` prints the result out. it's safer because it only applies the operation on lines with a `KEY` in it. – minghua Jul 19 '19 at 16:53
8

A slight variation on glenn jackman's answer using paste: if the value for the -d delimiter option contains more than one character, paste cycles through the characters one by one, and combined with the -s options keeps doing that while processing the same input file.

This means that we can use whatever we want to have as the separator plus the escape sequence \n to merge two lines at a time.

Using a comma:

$ paste -s -d ',\n' infile
KEY 4048:1736 string,3
KEY 0:1772 string,1
KEY 4192:1349 string,1
KEY 7329:2407 string,2
KEY 0:1774 string,1

and the dollar sign:

$ paste -s -d '$\n' infile
KEY 4048:1736 string$3
KEY 0:1772 string$1
KEY 4192:1349 string$1
KEY 7329:2407 string$2
KEY 0:1774 string$1

What this cannot do is use a separator consisting of multiple characters.

As a bonus, if the paste is POSIX compliant, this won't modify the newline of the last line in the file, so for an input file with an odd number of lines like

KEY 4048:1736 string
3
KEY 0:1772 string

paste won't tack on the separation character on the last line:

$ paste -s -d ',\n' infile
KEY 4048:1736 string,3
KEY 0:1772 string
Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
7

"ex" is a scriptable line editor that is in the same family as sed, awk, grep, etc. I think it might be what you are looking for. Many modern vi clone/successors also have a vi mode.

 ex -c "%g/KEY/j" -c "wq" data.txt

This says for each line, if it matches "KEY" perform a j oin of the following line. After that command completes (against all lines), issue a w rite and q uit.

Justin
  • 397
  • 7
  • 17
5

Another solutions using vim (just for reference).

Solution 1:

Open file in vim vim filename, then execute command :% normal Jj

This command is very easy to understand:

  • % : for all the lines,
  • normal : execute normal command
  • Jj : execute Join command, then jump to below line

After that, save the file and exit with :wq

Solution 2:

Execute the command in shell, vim -c ":% normal Jj" filename, then save the file and exit with :wq.

Xianzhong Wang
  • 111
  • 1
  • 6
5

You can use awk like this to combine ever 2 pair of lines:

awk '{ if (NR%2 != 0) line=$0; else {printf("%s %s\n", line, $0); line="";} } \
     END {if (length(line)) print line;}' flle
anubhava
  • 761,203
  • 64
  • 569
  • 643
5

If Perl is an option, you can try:

perl -0pe 's/(.*)\n(.*)\n/$1 $2\n/g' file.txt
andrefs
  • 555
  • 4
  • 14
  • Does the `-0` tell perl to set the record separator (`$/)` to null, so that we can span multiple lines in our matching pattern. The manpages are a bit too technical for me to figure out what it means in practice. – Sridhar Sarnobat May 07 '17 at 21:52
4

Another approach using vim would be:

:g/KEY/join

This applies a join (to the line below it) to all lines that have the word KEY in it. Result:

KEY 4048:1736 string 3
KEY 0:1772 string 1
KEY 4192:1349 string 1
KEY 7329:2407 string 2
KEY 0:1774 string 1
David542
  • 104,438
  • 178
  • 489
  • 842
3

You can also use the following vi command:

:%g/.*/j
Jdamian
  • 3,015
  • 2
  • 17
  • 22
  • Or even `:%g//j` since all you need is a match for the *join* to be executed, and a null string is still a valid regex. – ghoti Sep 18 '14 at 13:35
  • 1
    @ghoti, In Vim, when using just `//`, the previous search pattern will be used instead. If there is no previous pattern, Vim simply reports an error and do nothing. Jdamian's solution works all the time. – Tzunghsing David Wong Sep 20 '16 at 16:54
  • 1
    @TzunghsingDavidWong - that's a good pointer for vim users. Handily for me, neither the question nor this answer mentioned vim. – ghoti Sep 20 '16 at 17:44
3
cat input.txt
KEY 4048:1736 string
3
KEY 0:1772 string
1
KEY 4192:1349 string
1
KEY 7329:2407 string
2
KEY 0:1774 string
1
paste -sd ' \n' input.txt
KEY 4048:1736 string 3
KEY 0:1772 string 1
KEY 4192:1349 string 1
KEY 7329:2407 string 2
KEY 0:1774 string 1
paste -sd ' \n' input.txt | rev | sed 's/ / , /' | rev
KEY 4048:1736 string , 3
KEY 0:1772 string , 1
KEY 4192:1349 string , 1
KEY 7329:2407 string , 2
KEY 0:1774 string , 1
Weihang Jian
  • 7,826
  • 4
  • 44
  • 55
1
nawk '$0 ~ /string$/ {printf "%s ",$0; getline; printf "%s\n", $0}' filename

This reads as

$0 ~ /string$/  ## matches any lines that end with the word string
printf          ## so print the first line without newline
getline         ## get the next line
printf "%s\n"   ## print the whole line and carriage return
New Alexandria
  • 6,951
  • 4
  • 57
  • 77
1

In the case where I needed to combine two lines (for easier processing), but allow the data past the specific, I found this to be useful

data.txt

string1=x
string2=y
string3
string4
cat data.txt | nawk '$0 ~ /string1=/ { printf "%s ", $0; getline; printf "%s\n", $0; getline } { print }' > converted_data.txt

output then looks like:

converted_data.txt

string1=x string2=y
string3
string4
Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
Ben Taylor
  • 11
  • 1
0

Simplest way is here:

  1. Remove even lines and write it in some temp file 1.
  2. Remove odd lines and write it in some temp file 2.
  3. Combine two files in one by using paste command with -d (means delete space)

sed '0~2d' file > 1 && sed '1~2d' file > 2 && paste -d " " 1 2
jaypal singh
  • 74,723
  • 23
  • 102
  • 147
Serg
  • 1
0
perl -0pE 's{^KEY.*?\K\s+(\d+)$}{ $1}msg;' data.txt > data_merged-lines.txt

-0 gobbles the whole file instead of reading it line-by-line;
pE wraps code with loop and prints the output, see details in http://perldoc.perl.org/perlrun.html;
^KEY match "KEY" in the beginning of line, followed by non-greedy match of anything (.*?) before sequence of

  1. one or more spaces \s+ of any kind including line breaks;
  2. one or more digit (\d+) which we capture and later re-insert as $1;

followed by the end of line $.

\K conveniently excludes everything on its left hand side from substitution so { $1} replaces only 1-2 sequence, see http://perldoc.perl.org/perlre.html.

Onlyjob
  • 5,692
  • 2
  • 35
  • 35
0

A more-general solution (allows for more than one follow-up line to be joined) as a shell script. This adds a line between each, because I needed visibility, but that is easily remedied. This example is where the "key" line ended in : and no other lines did.

#!/bin/bash
#
# join "The rest of the story" when the first line of each   story
# matches $PATTERN
# Nice for looking for specific changes in bart output
#

PATTERN='*:';
LINEOUT=""
while read line; do
    case $line in
        $PATTERN)
                echo ""
                echo $LINEOUT
                LINEOUT="$line"
                        ;;
        "")
                LINEOUT=""
                echo ""
                ;;

        *)      LINEOUT="$LINEOUT $line"
                ;;
    esac        
done
-1

Try the following line:

while read line1; do read line2; echo "$line1 $line2"; done <old.txt>new_file

Put delimiter in-between

"$line1 $line2";

e.g. if the delimiter is |, then:

"$line1|$line2";
coatless
  • 20,011
  • 13
  • 69
  • 84
Suman
  • 476
  • 5
  • 7
  • This answer is not adding anything not provided in [Hai Vu's answer](http://stackoverflow.com/a/9607002/1983854) that was posted 4 years before yours. – fedorqui Jun 16 '16 at 10:25
  • 1
    I agree partially, I try to add explanation and more generic It will not edit old file as well. Thanks for your suggestion – Suman Sep 24 '16 at 07:01