Làm thế nào để hợp nhất mỗi hai dòng thành một từ dòng lệnh?


151

Tôi có một tập tin văn bản với định dạng sau. Dòng đầu tiên là "KEY" và dòng thứ hai là "GIÁ TRỊ".

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

Tôi cần giá trị trong cùng dòng với khóa. Vì vậy, đầu ra sẽ trông như thế này ...

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

Sẽ tốt hơn nếu tôi có thể sử dụng một số dấu phân cách như $hoặc ,:

KEY 4048:1736 string , 3

Làm cách nào để hợp nhất hai dòng thành một?


Có rất nhiều cách để làm điều này! Tôi đã thực hiện một băng ghế nhỏ với pr, paste, awk, xargs, sedpure bash ! ( xargslà chậm hơn, chậm hơn bash !)
F. Hauri

Câu trả lời:


182

ôi

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

lưu ý, có một dòng trống ở cuối đầu ra.

sed:

sed 'N;s/\n/ /' yourFile

Không hoạt động với đầu ra màu. Tôi đã thử mọi thứ trong Q & A này và không có gì hoạt động khi đầu ra có màu ansi. Đã thử nghiệm trên Ubuntu 13.04
Leo Gallucci

1
@elgalu: Bởi vì màu ANSI chỉ là một loạt các kết hợp ký tự thoát. Thực hiện hexedit trên một đầu ra như vậy, để xem những gì bạn có.
not2qubit

7
Giải pháp awk này có thể phá vỡ nếu các printfchuỗi mở rộng như %sđược tìm thấy bên trong $0. Thất bại đó có thể tránh được như thế này:'NR%2{printf "%s ",$0;next;}1'
ghoti

9
Bởi vì thật sự rất khó để google, 1sau khi kết thúc cú đúp có ý nghĩa gì?
erikbwork

5
@ erikb85 Ở đây bạn đi stackoverflow.com/questions/24643240/ từ
Viraj

243

paste là tốt cho công việc này:

paste -d " "  - - < filename

10
Tôi nghĩ rằng đây là giải pháp tốt nhất được trình bày, mặc dù sử dụng cả sed và awk. Với đầu vào là một số dòng lẻ, giải pháp awk của Kent bỏ qua dòng mới cuối cùng, giải pháp sed của anh ta bỏ qua toàn bộ dòng cuối cùng, và giải pháp của tôi lặp lại dòng cuối cùng. pastemặt khác, cư xử hoàn hảo. +1.
ghoti

8
Tôi thường sử dụng cutnhưng luôn quên về paste. Nó đá cho vấn đề này. Tôi cần kết hợp tất cả các dòng từ stdin và làm điều đó dễ dàng với paste -sd ' ' -.
Clint Pachl

4
Đơn giản và đẹp!
krlmlr

8
nên -stdin trung bình, vì vậy paste - -có nghĩa là đọc từ stdin, sau đó đọc từ stdin, bạn có thể stack như nhiều người như bạn muốn tôi mong đợi.
ThorSummoner

1
Có, @ThorSummoner ... Tôi đã phải dán ba dòng vào một dòng duy nhất và đã dán - - - và nó hoạt động hoàn hảo.
Daniel Goldfarb

35

Thay thế cho sed, awk, grep:

xargs -n2 -d'\n'

Điều này là tốt nhất khi bạn muốn tham gia N dòng và bạn chỉ cần đầu ra được phân tách không gian.

Câu trả lời ban đầu của tôi là xargs -n2phân tách trên các từ hơn là các dòng. -dcó thể được sử dụng để phân chia đầu vào bởi bất kỳ ký tự đơn lẻ nào.


4
Đây là một phương pháp hay, nhưng nó hoạt động trên các từ, không phải các dòng. Để làm cho nó hoạt động trên các dòng, có thể thêm-d '\n'
Don hatch

2
Ồ, tôi là xargsngười dùng thường xuyên nhưng không biết điều này. Mẹo tuyệt vời.
Sridhar Sarnobat

1
Tôi thích điều này. Thật sạch sẽ.
Alexander Guo

28

Có nhiều cách để giết một con chó hơn là treo cổ. [1]

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

Đặt bất cứ dấu phân cách nào bạn thích bên trong dấu ngoặc kép.


Người giới thiệu:

  1. Ban đầu "Rất nhiều cách để lột da con mèo", trở lại với một biểu hiện già hơn, có khả năng bắt nguồn từ vật nuôi.

Tôi thích giải pháp này.
luis.espinal

5
Là một chủ sở hữu mèo, tôi không đánh giá cao sự hài hước này.
witkacy26

4
@ witkacy26, Điều chỉnh biểu thức theo mối quan tâm của bạn.
ghoti

Tôi thích giải pháp awk này nhưng tôi không hiểu cách thức hoạt động của nó: S
Rubendob

@Rubendob - awk đọc từng dòng đầu vào và đặt nó vào biến $0. Các getlinelệnh cũng lấy "tiếp theo" dòng đầu vào và đặt nó vào $0. Vì vậy, câu lệnh đầu tiên lấy dòng đầu tiên và lệnh in kết hợp những gì được lưu trong biến keyvới một chuỗi chứa dấu phẩy, cùng với dòng được tìm nạp bằng cách sử dụng getline. Rõ ràng hơn? :)
ghoti

12

Đây là giải pháp của tôi trong bash:

while read line1; do read line2; echo "$line1, $line2"; done < data.txt

11

Mặc dù có vẻ như các giải pháp trước đó sẽ hoạt động, nếu một sự bất thường duy nhất xảy ra trong tài liệu, đầu ra sẽ chuyển thành từng mảnh. Dưới đây là một chút an toàn hơn.

sed -n '/KEY/{
N
s/\n/ /p
}' somefile.txt

3
Tại sao nó an toàn hơn? Không gì /KEY/làm gì? Làm gì pcuối cùng?
Stewart

các /KEY/tìm kiếm cho dòng với KEY. các pkết quả in ra. an toàn hơn vì nó chỉ áp dụng thao tác trên các dòng có một KEYtrong đó.
minghua

11

Đây là một cách khác với 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

Theo chỉ định của Ed Morton trong các bình luận, tốt hơn là thêm niềng răng cho an toàn và parens cho tính di động.

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

ORSlà viết tắt của Dấu tách bản ghi đầu ra. Những gì chúng tôi đang làm ở đây là kiểm tra một điều kiện bằng cách sử dụng NRlưu trữ số dòng. Nếu modulo của NRlà một giá trị thực (> 0) thì chúng ta đặt Dấu tách trường đầu ra thành giá trị của FS(Dấu tách trường) mà theo mặc định là khoảng trắng, chúng ta sẽ gán giá trị của RS(Dấu tách bản ghi) là dòng mới.

Nếu bạn muốn thêm ,làm dấu phân cách thì hãy sử dụng như sau:

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

1
Chắc chắn là cách tiếp cận đúng nên +1 nhưng tôi tự hỏi điều kiện nào được đánh giá để gọi hành động mặc định của việc in bản ghi. Có phải đó là nhiệm vụ đã thành công? Có phải nó đơn giản ORSvà điều đó được xử lý truevì ORS nhận được một giá trị không phải là 0 hoặc chuỗi null và đánh giá chính xác rằng nó phải là một sting thay vì so sánh số? Có phải cái gì khác không? Tôi thực sự không chắc chắn và vì vậy tôi đã viết nó như là awk '{ORS=(NR%2?FS:RS)}1' file. Tôi đã ngoặc đơn biểu thức ternary để đảm bảo tính di động.
Ed Morton

1
@EdMorton Vâng, tôi vừa thấy vài câu hỏi về câu trả lời này sắp cập nhật nó để bao gồm cả niềng răng cho an toàn. Sẽ thêm parens là tốt.
jaypal singh

7

"ex" là một trình soạn thảo dòng có thể viết kịch bản trong cùng một gia đình như sed, awk, grep, v.v. Tôi nghĩ đó có thể là những gì bạn đang tìm kiếm. Nhiều bản sao / người kế vị hiện đại cũng có chế độ vi.

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

Điều này nói cho mỗi dòng, nếu nó khớp với "KEY" thì thực hiện j oin của dòng sau. Sau khi lệnh đó hoàn thành (đối với tất cả các dòng), hãy đưa ra một nghi thức wq uit.


4

Nếu Perl là một tùy chọn, bạn có thể thử:

perl -0pe 's/(.*)\n(.*)\n/$1 $2\n/g' file.txt

Có phải -0nói với perl để thiết lập dấu tách bản ghi ( $/)thành null, để chúng ta có thể kéo dài nhiều dòng trong mẫu phù hợp của mình không. Các trang này hơi quá kỹ thuật đối với tôi để tìm hiểu ý nghĩa của nó trong thực tế.
Sridhar Sarnobat

4

Bạn có thể sử dụng awk như thế này để kết hợp 2 cặp dòng:

awk '{ if (NR%2 != 0) line=$0; else {printf("%s %s\n", line, $0); line="";} } \
     END {if (length(line)) print line;}' flle

4

Một giải pháp khác sử dụng vim (chỉ để tham khảo).

Giải pháp 1 :

Mở tệp trong vim vim filename, sau đó thực hiện lệnh:% normal Jj

Lệnh này rất dễ hiểu:

  • %: cho tất cả các dòng,
  • bình thường: thực hiện lệnh bình thường
  • Jj: thực hiện lệnh Tham gia, sau đó nhảy xuống dòng bên dưới

Sau đó, lưu tệp và thoát với :wq

Giải pháp 2 :

Thực hiện lệnh trong shell vim -c ":% normal Jj" filename, sau đó lưu tệp và thoát với :wq.


Cũng norm!mạnh mẽ hơn normaltrong trường hợp Jđược ánh xạ lại. +1 cho giải pháp vim.
qeatzy

@qeatzy Cảm ơn bạn đã dạy tôi điều đó. Rất vui mừng khi biết điều đó. ^ _ ^
Jensen

3

Bạn cũng có thể sử dụng lệnh vi sau:

:%g/.*/j

Hoặc thậm chí :%g//jvì tất cả bạn cần là một trận đấu cho tham gia được thực thi, và một chuỗi rỗng vẫn là một regex hợp lệ.
ghoti

1
@ghoti, Trong Vim, khi chỉ sử dụng //, mẫu tìm kiếm trước đó sẽ được sử dụng thay thế. Nếu không có mẫu trước đó, Vim chỉ cần báo lỗi và không làm gì cả. Giải pháp của Jdamian hoạt động mọi lúc.
Tzunghsing David Wong

1
@TzunghsingDavidWong - đó là một con trỏ tốt cho người dùng vim. Handily cho tôi, cả câu hỏi và câu trả lời này đều không đề cập đến vim.
ghoti

3

Một thay đổi nhỏ về câu trả lời của glenn jackman bằng cách sử dụng paste: nếu giá trị cho -dtùy chọn dấu phân cách chứa nhiều hơn một ký tự, chuyển pastequa từng ký tự và kết hợp với các -stùy chọn tiếp tục thực hiện điều đó trong khi xử lý cùng một tệp đầu vào.

Điều này có nghĩa là chúng ta có thể sử dụng bất cứ thứ gì chúng ta muốn có như dấu phân cách cộng với chuỗi thoát \n để hợp nhất hai dòng cùng một lúc.

Sử dụng dấu phẩy:

$ 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

và ký hiệu đô la:

$ 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

Điều này không thể làm là sử dụng một dấu phân cách bao gồm nhiều ký tự.

Như một phần thưởng, nếu pastetuân thủ POSIX, điều này sẽ không sửa đổi dòng mới của dòng cuối cùng trong tệp, vì vậy đối với một tệp đầu vào có số lượng dòng lẻ như

KEY 4048:1736 string
3
KEY 0:1772 string

paste sẽ không giải quyết ký tự phân tách trên dòng cuối cùng:

$ paste -s -d ',\n' infile
KEY 4048:1736 string,3
KEY 0:1772 string

1
nawk '$0 ~ /string$/ {printf "%s ",$0; getline; printf "%s\n", $0}' filename

Cái này đọc là

$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

1

Trong trường hợp tôi cần kết hợp hai dòng (để xử lý dễ dàng hơn), nhưng cho phép dữ liệu vượt quá mức cụ thể, tôi thấy điều này hữu ích

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

đầu ra sau đó trông như:

convert_data.txt

string1=x string2=y
string3
string4

1

Một cách tiếp cận khác sử dụng vim sẽ là:

:g/KEY/join

Điều này áp dụng một join(cho dòng bên dưới nó) cho tất cả các dòng có từ KEYtrong đó. Kết quả:

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

0

Cách đơn giản nhất là ở đây:

  1. Xóa các dòng chẵn và viết nó trong một số tệp tạm thời 1.
  2. Xóa các dòng lẻ và viết nó trong một số tệp tạm thời 2.
  3. Kết hợp hai tệp trong một bằng cách sử dụng lệnh dán với -d (có nghĩa là xóa không gian)

sed '0~2d' file > 1 && sed '1~2d' file > 2 && paste -d " " 1 2

0
perl -0pE 's{^KEY.*?\K\s+(\d+)$}{ $1}msg;' data.txt > data_merged-lines.txt

-0ngấu nghiến toàn bộ tập tin thay vì đọc từng dòng một;
pEbọc mã với vòng lặp và in kết quả đầu ra, xem chi tiết trong http://perldoc.perl.org/perlrun.html ;
^KEYkhớp "KEY" ở đầu dòng, theo sau là kết hợp không tham lam của bất cứ thứ gì ( .*?) trước chuỗi

  1. một hoặc nhiều khoảng trắng \s+của bất kỳ loại nào, kể cả ngắt dòng;
  2. một hoặc nhiều chữ số (\d+)mà chúng tôi chụp và sau đó chèn lại dưới dạng $1;

tiếp theo là cuối dòng $.

\Kthuận tiện loại trừ mọi thứ ở phía bên trái của nó khỏi sự thay thế, vì vậy { $1}chỉ thay thế 1-2 chuỗi, xem http://perldoc.perl.org/perlre.html .


0

Một giải pháp tổng quát hơn (cho phép nhiều hơn một dòng tiếp theo được nối) dưới dạng tập lệnh shell. Điều này thêm một dòng giữa mỗi, bởi vì tôi cần tầm nhìn, nhưng điều đó dễ dàng được khắc phục. Ví dụ này là nơi dòng "chìa khóa" kết thúc: và không có dòng nào khác làm được.

#!/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

Hãy thử dòng sau:

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

Đặt dấu phân cách ở giữa

"$line1 $line2";

ví dụ: nếu dấu phân cách là |, thì:

"$line1|$line2";

Câu trả lời này không thêm bất cứ điều gì không được cung cấp trong câu trả lời của Hải Vũ đã được đăng 4 năm trước bạn.
fedorqui 'SO ngừng làm hại'

Tôi đồng ý một phần, tôi cố gắng thêm lời giải thích và chung chung hơn Nó cũng sẽ không chỉnh sửa tập tin cũ. Cảm ơn lời đề nghị của bạn
Suman

-2

Bạn có thể sử dụng xargsnhư thế này:

xargs -a file

% cat> tệp abc% xargs -a tệp abc% Hoạt động với tôi
RSG

Nó làm một cái gì đó, có, nhưng không phải những gì OP yêu cầu. Cụ thể, nó tham gia càng nhiều dòng càng tốt. Bạn thực sự có thể có được những gì bạn muốn với xargs -n 2nhưng câu trả lời này không giải thích điều này cả.
tripleee
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.