Biến danh sách thành một dòng với dấu phân cách


16

Tôi phải lấy một danh sách (tải) địa chỉ IP theo định dạng này:

 134.27.128.0
 111.245.48.0
 109.21.244.0

và biến chúng thành định dạng này với một đường ống ở giữa (IP được tạo thành)

134.27.128.0 | 111.245.48.0 | 109.21.244.0 | 103.22.200.0/22

Tôi nghĩ rằng đó là một lệnh tìm và thay thế như thế sednhưng tôi không thể làm cho nó hoạt động được.


3
Bạn chỉ muốn trxiên dòng mới vào |đường ống? Giống như <ipfile tr \\n \| >outfile?
mikeerv

Là không gian xung quanh |cần thiết?
cuonglm

2
@uslesslinuxman - không. Bạn sẽ cần chuyển hướng đầu vào <. Vì vậy <mydoc tr \\n \| >mydoc2. Nhưng điều đó sẽ không giúp bạn có được không gian. Đối với những người này, có lẽ giải pháp nhanh nhất làpaste -d' | ' mydoc /dev/null /dev/null >mydoc2
mikeerv

1
@mikeerv: Tôi không nghĩ nó sẽ hoạt động. pasteghi các dòng tương ứng từ mỗi tệp. Nếu không -s, bạn sẽ lấy lại số dòng bạn có trong tệp.
cuonglm

2
@ val0x00ff: Tôi mời bạn đọc unix.stackexchange.com/q/169716/38906
cuonglm

Câu trả lời:


15

Sử dụng sed, dựa trên Nổi tiếng Sed One-Liners Giải thích, Phần I: : 39. Nối một dòng vào sau nếu nó kết thúc với một dấu chéo ngược "\" (ngoại trừ ở đây chúng ta bỏ qua những phần về xuyệc ngược, và thay thế các \ndòng mới với |dải phân cách yêu cầu ):

sed -e :a -e '$!N; s/\n/ | /; ta' mydoc > mydoc2

nên sản xuất trong mydoc2

134.27.128.0 |  111.245.48.0 |  109.21.244.0

@don_crissti xin lỗi đó là một loại - đã được sửa, cảm ơn
Steeldo

Điều này không thực sự hoạt động trong thực tế, không may. Ít nhất, không cho các luồng không giới hạn. Khi bạn làm điều này, bạn phải nuốt toàn bộ dòng đầu vào của mình một dòng và không thể ghi ngay cả một byte của nó vào đầu ra cho đến khi bạn tiêu hóa hết tất cả - tất cả biến thành một dòng. Nó khó sử dụng và dễ bị segfault.
mikeerv

Một triệu IP là <16 triệu, bạn cần một danh sách cực kỳ lớn để vượt qua giới hạn ở đây. Sử dụng tìm kiếm để phát hiện eof có nhiều vấn đề hơn, vì điều này sẽ chạy O (N ^ 2) trên kích thước tệp đầu vào. sed 'H;1h;$!d;x;s/\n/ | /g'là tuyến tính.
jthill

@jthill - POSIX chỉ đảm bảo sedkhông gian mẫu là 8K; đó là rất nhiều ít hơn 16 triệu.
mikeerv

9

Tôi tò mò muốn xem một số trong số này (+ một số lựa chọn thay thế) hoạt động nhanh như thế nào với một tệp khá lớn ( 163MiB, IPmỗi tệp trên một dòng, ~ 13 triệu dòng):

wc -l < iplist
13144256

Kết quả (với sync; echo 3 > /proc/sys/vm/drop_cachessau mỗi lệnh; tôi lặp lại các thử nghiệm - theo thứ tự ngược lại - sau một vài giờ nhưng sự khác biệt là không đáng kể; cũng lưu ý rằng tôi đang sử dụng gnu sed):

Người thép :
Rất chậm. Bị hủy bỏ sau hai phút chờ đợi ... vì vậy không có kết quả nào cho việc này.

cuonglm :

awk 'FNR!=1{print l}{l=$0};END{ORS="";print l}' ORS=' | ' iplist

real    0m3.672s

perl -pe 's/\n/ | / unless eof' iplist

real    0m12.444s

mikeerv :

paste -d\  /dev/null iplist /dev/null | paste -sd\| - 

real    0m0.983s

tháng năm :

sed 'H;1h;$!d;x;s/\n/ | /g' iplist

real    0m4.903s

Avinash Raj :

time python2.7 -c'
import sys
with open(sys.argv[1]) as f:
    print " | ".join(line.strip() for line in f)' iplist

real    0m3.434s

val0x00ff :

while read -r ip; do printf '%s | ' "$ip"; done < iplist

real    3m4.321s

mà phương tiện 184.321s. Không có gì đáng ngạc nhiên, điều này chậm hơn 200 lần so với giải pháp của mikeerv .


Đây là một số cách khác với
awk:

awk '$1=$1' RS= OFS=' | ' iplist

real    0m4.543s

awk '{printf "%s%s",sep,$0,sep=" | "} END {print ""}' iplist

real    0m5.511s

perl:

perl -ple '$\=eof()?"\n":" | "' iplist

real    0m9.646s

xargs:

xargs <iplist printf ' | %s' | cut -c4-

real    0m6.326s

sự kết hợp của đầu + dán + tr + mèo:

{ head -n -1 | paste -d' |' - /dev/null /dev/null | tr \\n \ ; cat ; } <iplist

real    0m0.991s

Nếu bạn có GNU coreutilsvà nếu danh sách IP của bạn không thực sự lớn (giả sử lên tới 50000 IP), bạn cũng có thể làm điều này với pr:

pr -$(wc -l infile) -tJS' | ' -W1000000 infile >outfile

Ở đâu

-$(wc -l infile)         # no. of columns (= with no. of lines in your file)
-t                       # omit page headers and trailers
-J                       # merge lines
-S' | '                  # separate columns by STRING
-W1000000                # set page width

ví dụ: đối với tệp 6 dòng:

134.28.128.0
111.245.28.0
109.245.24.0
128.27.88.0
122.245.48.0
103.44.204.0

lệnh:

pr -$(wc -l <infile) -tJS' | ' -W1000 infile

đầu ra:

134.28.128.0 | 111.245.28.0 | 109.245.24.0 | 128.27.88.0 | 122.245.48.0 | 103.44.204.0

don - bạn cũng có thể thêm vào gợi ý trong câu hỏi của @ val0x00ff cho while ... readvòng lặp không? Tôi tò mò muốn xem những gì 163k read()write()các cuộc gọi chuyển thành điểm chuẩn. Bằng cách này, câu trả lời tuyệt vời.
mikeerv

1
@mikeerv - không vấn đề gì, tôi sẽ làm điều đó ( mặc dù nó sẽ rất chậm ).
don_crissti

Đó là một liên kết thực sự tuyệt vời. Tôi đặc biệt thích rằng tác giả cũng cung cấp một liên kết đến một điểm chuẩn 6 tuổi tương tự ở đó. Bạn có nhận thấy rằng seddường như đã cải thiện vị thế của nó trong thời gian đó (và có lẽ chỉ có một vài thay đổi đối với công cụ regrec của nó) nhưng grepdường như đã giảm đáng kể về hiệu suất của nó (đặc biệt là đối với các dòng dài hơn) ? Tôi tự hỏi nếu perlbổ sung vào động cơ của nó có bất cứ mang về những kết quả ... Nó cũng gọn gàng mà dashkhông phải là không đáy . Ở bashđây có thể sẽ chậm hơn nhiều so với dự kiến ​​chung IFS=.
mikeerv

hmm ... liên kết đó là một chỉ số mạnh khác mà tôi thực sự cần phải khóa xuống và học C để cuối cùng tôi có thể bắt đầu sử dụng lexđúng cách.
mikeerv

8

Bạn có thể sử dụng awk :

awk 'FNR!=1{print l}{l=$0};END{ORS="";print l}' ORS=' | ' file > new_file

ORS=' | 'thiết lập các separator kỷ lục sản lượng để ' | 'thay cho dòng mới.

hoặc chỉnh sửa tại chỗ với perl:

perl -pe 's/\n/ | / unless eof' file

cảm ơn người đàn ông Tôi chỉ học cách làm pasteviệc. Nhiều đánh giá cao.
mikeerv

@mikeerv: Bạn được chào đón. như don_crissti thể hiện trong tiêu chuẩn của mình, pastegiải pháp là giải pháp nhanh nhất.
cuonglm

Đầu ra không kết thúc với một dòng mới. Bạn có thể phải thay thế ORS=""bên trong ENDkhối bằng ORS="\n"nó.
phk

4

Vì vậy, tôi đã hoàn toàn sai - và câu hỏi này đã dạy tôi rất nhiều về paste. Như cuonglm ghi chú chính xác, trừ khi bạn có pastemột tệp trong -serial, bạn sẽ luôn kết thúc với \newline cuối cùng từ danh sách lưu trữ của bạn được thêm vào đầu ra khi nó được viết. Tôi đã nhầm lẫn khi tin rằng paste -shành vi là chế độ mặc định của nó - và đây là một quan niệm sai lầm, rõ ràng busybox pastelà rất vui khi củng cố. Lệnh sau không hoạt động như quảng cáo w / busybox:

paste -d'|  ' - - infile </dev/null >outfile

Nó không hoạt động theo thông số kỹ thuật, mặc dù. Một triển khai chính xác pastevẫn sẽ nối thêm một \newline kéo dài cho mỗi chuỗi được viết. Tuy nhiên, đó không phải là vấn đề lớn sau tất cả:

paste -d\  - infile - </dev/null | paste -sd\| - >outfile

@don_crissti - nguy hiểm. máy tính bảng ngu ngốc. Tôi đoán điều rõ ràng phải làm là hai miếng dán.
mikeerv

1
Vâng, tôi đã có prý định nhưng dường như nó hết hơi với các tệp đầu vào lớn nên tôi thực sự không thể kiểm tra tốc độ nhưng với các tệp có độ dài hợp lý thì nó hoạt động tốt. Giải pháp của bạn là nhanh nhất (không có gì bất ngờ - pastethực sự nhanh), xem bài viết của tôi.
don_crissti

3

Tận dụng vim :

vim -n -u NONE -c '1,$-1s/\n/ | /g|wq!' data

Giải trình:

-n vô hiệu hóa tập tin trao đổi

-u NONE được sử dụng để bỏ qua tất cả các khởi tạo.

-c {command} thực hiện các lệnh sau khi tập tin đã được đọc.

1,$-1s/\n/ | /gs/\n/ | /g(thay thế dòng mới bằng không gian ống không gian) cho phạm vi 1,$-1s(dòng thứ nhất đến dòng cuối cùng - 1)

wq! buộc viết và bỏ


Ghi chú:

Tùy thuộc vào mức độ lớn của tập tin của bạn, đây có thể là một ý tưởng tồi.


1
Tôi cảm ơn tất cả các bạn, vì về cơ bản, gần như tất cả các lệnh này đều hoạt động cho những gì tôi cần phải đạt được. Tôi biết nơi nào sẽ đến bây giờ nếu (khi) tôi lại bị mắc kẹt. Cảm ơn
uslesslinuxman

3

một lớp lót với tr và sed:

cat file | tr '\n' '|' | sed 's/||$/\n/'
134.27.128.0|111.245.48.0|109.21.244.0

Tại sao xóa 2 đường ống? Sẽ chỉ có 2 ở cuối nếu đầu vào kết thúc bằng một dòng trống (hai dòng mới).
JigglyNaga

2

Qua trăn.

$ python -c '
import sys
with open(sys.argv[1]) as f:
    print " | ".join(line.strip() for line in f)' file

không gian trước đây printlà rất quan trọng.


2

Đây là một cái khác sử dụng xxd

xxd -c1 -ps data | sed '$!s/0a/207c20/' | xxd -r -ps

2

Để hoàn thiện, đây là một awkgiải pháp dựa trên cơ sở khác , giải pháp này hoàn toàn không sử dụng ORS:

awk 'BEGIN { ORS="" } { print p$0; p=" | " } END { print "\n" }' file > new_file

Để được giải thích, hãy xem bài đăng của tôi tại /unix//a/338121/117599 .

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.