Ubah daftar menjadi satu baris dengan pembatas

17

Saya harus mengambil daftar (banyak) alamat IP dalam format ini:

 134.27.128.0
 111.245.48.0
 109.21.244.0

dan mengubahnya menjadi format ini dengan pipa di antara (IP dibuat)

134.27.128.0 | 111.245.48.0 | 109.21.244.0 | 103.22.200.0/22

Saya pikir itu adalah menemukan dan mengganti perintah seperti sedtetapi saya tidak bisa membuatnya bekerja.

uslesslinuxman
sumber
3
Anda hanya ingin trmenguraikan baris baru ke |pipa? Suka <ipfile tr \\n \| >outfile?
mikeserv
Apakah ruang di sekitar |dibutuhkan?
cuonglm
2
@uselesslinuxman - no. Anda membutuhkan pengalihan input <. Jadi <mydoc tr \\n \| >mydoc2. Tapi itu tidak akan memberimu ruang. Bagi mereka, mungkin solusi tercepat adalahpaste -d' | ' mydoc /dev/null /dev/null >mydoc2
mikeserv
1
@ mikeserv: Saya tidak berpikir itu akan berhasil. pastemenulis baris yang sesuai dari setiap file. Tanpa -s, Anda akan mendapatkan kembali jumlah baris yang Anda miliki di file.
cuonglm
2
@ val0x00ff: Saya mengundang Anda untuk membaca unix.stackexchange.com/q/169716/38906
cuonglm

Jawaban:

16

Menggunakan sed, berdasarkan Terkenal Sed Satu-Liners Dijelaskan, Bagian I: : 39. Append garis ke yang berikutnya jika berakhir dengan backslash "\" (kecuali di sini kita mengabaikan bagian tentang garis miring terbalik, dan mengganti \nbaris dengan diperlukan |pemisah):

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

harus menghasilkan mydoc2

134.27.128.0 |  111.245.48.0 |  109.21.244.0
Steeldriver
sumber
@don_crissti maaf itu adalah tipe - dikoreksi, terima kasih
steeldriver
Sayangnya, ini sebenarnya tidak berhasil. Setidaknya, tidak untuk streaming tanpa batas. Ketika Anda melakukan ini, Anda harus menelan seluruh input Anda satu kali dan tidak dapat menulis bahkan satu byte pun untuk output sampai Anda telah mencerna semuanya - semuanya berubah menjadi satu baris. Ini berat dan cenderung segfault.
mikeserv
Satu juta IP adalah <16 juta, Anda perlu daftar yang sangat besar untuk melampaui batas di sini. Menggunakan pencarian untuk deteksi eof lebih bermasalah, seperti ini akan menjalankan O (N ^ 2) pada ukuran file input. sed 'H;1h;$!d;x;s/\n/ | /g'linear.
jthill
@ jthill - POSIX hanya menjamin sedruang pola 8K; itu jauh lebih sedikit dari 16 juta.
mikeserv
9

Saya ingin tahu bagaimana beberapa dari ini (+ beberapa alternatif) bekerja cepat dengan file yang agak besar ( 163MiB, satu IPper baris, ~ 13 juta baris):

wc -l < iplist
13144256

Hasil (dengan sync; echo 3 > /proc/sys/vm/drop_cachessetelah setiap perintah; Saya mengulangi tes - dalam urutan terbalik - setelah beberapa jam tetapi perbedaannya dapat diabaikan; juga perhatikan bahwa saya menggunakan gnu sed):

steeldriver :
Sangat lambat. Dibatalkan setelah dua menit menunggu ... jadi tidak ada hasil untuk yang satu ini.

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

mikeserv :

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

real    0m0.983s

jthill :

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

dan

val0x00ff :

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

real    3m4.321s

yang artinya 184.321s. Tidak mengherankan, ini 200 kali lebih lambat dari solusi mikeserv .


Berikut ini beberapa cara lain dengan
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

kombinasi head + paste + tr + cat:

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

real    0m0.991s

Jika Anda memiliki GNU coreutilsdan jika daftar IP Anda tidak terlalu besar (katakanlah hingga 50000 IP), Anda juga dapat melakukan ini dengan pr:

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

dimana

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

misalnya untuk file 6-baris:

134.28.128.0
111.245.28.0
109.245.24.0
128.27.88.0
122.245.48.0
103.44.204.0

perintah:

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

output:

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_crissti
sumber
don - bisakah Anda juga menambahkan saran dalam pertanyaan dengan @ val0x00ff untuk while ... readloop? Saya ingin tahu apa yang diterjemahkan oleh 163k read()dan write()panggilan dalam patokan. Omong-omong, jawaban yang bagus.
mikeserv
1
@ mikeserv - tidak masalah, saya akan melakukannya (itu akan sangat lambat ).
don_crissti
Itu tautan yang sangat keren. Saya terutama suka bahwa penulis menawarkan tautan ke tolok ukur yang sama berusia 6 tahun di sana. Apakah Anda memperhatikan bahwa sedtampaknya telah meningkatkan kedudukannya pada waktu itu (dan mungkin hanya memiliki sedikit perubahan pada mesin regexpnya) tetapi greptampaknya secara dramatis tertinggal dalam kinerjanya (terutama untuk saluran yang lebih panjang) ? Saya ingin tahu apakah perlpenambahan pada mesinnya memiliki pengaruh pada hasil itu ... Ini juga rapi yang dashtidak buruk . Yang di bashsini mungkin akan jauh lebih lambat bersama yang umum IFS=.
mikeserv
hmm ... tautan itu adalah indikator kuat lainnya yang benar-benar harus saya tekankan dan pelajari C sehingga akhirnya saya dapat mulai menggunakan lexdengan benar.
mikeserv
8

Anda dapat menggunakan awk :

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

ORS=' | 'atur pemisah catatan keluaran menjadi ' | 'bukan baris baru.

atau edit di tempat dengan perl:

perl -pe 's/\n/ | / unless eof' file
cuonglm
sumber
Terima kasih sobat. Saya baru belajar cara pastekerjanya. sangat dihargai.
mikeserv
@ mikeserv: Sama-sama. seperti yang ditunjukkan don_crissti dalam patokannya, pastesolusinya adalah yang tercepat.
cuonglm
Output tidak berakhir dengan baris baru. Anda mungkin harus mengganti ORS=""di dalam ENDblok dengan ORS="\n"begitu.
phk
4

Jadi saya salah semuanya - dan pertanyaan ini telah mengajari saya banyak hal paste. Sebagai cuonglm dengan benar mencatat, kecuali jika Anda berada pastedi file dalam -serial, Anda akan selalu berakhir dengan garis terakhir \ndari daftar infile Anda ditambahkan ke output seperti yang tertulis. Saya keliru dalam kepercayaan bahwa paste -sperilaku adalah mode default - dan ini adalah kesalahpahaman yang, tampaknya busybox pastesenang untuk diperkuat. Perintah berikut ini berfungsi seperti yang diiklankan dengan busybox:

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

Namun, itu tidak bekerja sesuai dengan spesifikasi. Sebuah implementasi yang benar pastemasih akan menambahkan garis \nakhir untuk setiap urutan yang ditulis. Namun, itu bukan masalah besar:

paste -d\  - infile - </dev/null | paste -sd\| - >outfile
mikeserv
sumber
@don_crissti - dangit. tablet bodoh. Saya kira hal yang jelas untuk dilakukan adalah dua pasta.
mikeserv
1
Yah, saya ada prdalam pikiran tetapi ternyata itu kehabisan uap dengan file input besar jadi saya tidak bisa benar-benar menguji kecepatan tetapi dengan file yang masuk akal panjang itu bekerja OK. Solusi Anda sejauh ini yang tercepat (tidak mengherankan - pastesangat cepat), lihat posting saya.
don_crissti
4

satu-liner dengan tr dan sed:

cat file | tr '\n' '|' | sed 's/||$/\n/'
134.27.128.0|111.245.48.0|109.21.244.0
pengguna5337995
sumber
Mengapa menghapus 2 pipa trailing? Hanya akan ada 2 di akhir jika input diakhiri dengan baris kosong (dua baris baru).
JigglyNaga
3

Gunakan vim:

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

Penjelasan:

-n nonaktifkan file swap

-u NONE digunakan untuk melewati semua inisialisasi.

-c {command} jalankan perintah setelah file dibaca.

1,$-1s/\n/ | /gadalah s/\n/ | /g(ganti baris baru dengan spasi pipa ruang) untuk rentang 1,$-1s(baris 1 ke baris terakhir - 1)

wq! paksa menulis dan berhenti


catatan:

Bergantung pada seberapa besar file Anda sebenarnya, ini mungkin ide yang buruk.

FloHimself
sumber
1
Saya berterima kasih kepada Anda semua, karena pada dasarnya hampir semua perintah ini berfungsi untuk apa yang saya butuhkan untuk mencapainya. Saya tahu ke mana harus datang sekarang jika (kapan) saya terjebak lagi. Terima kasih
uselesslinuxman
2

Melalui python.

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

ruang sebelumnya printsangat penting.

Avinash Raj
sumber
2

Ini satu lagi menggunakan xxd

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

Demi kelengkapan, berikut ini adalah awksolusi berbasis- lain , yang ini tidak menggunakan ORSsama sekali:

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

Untuk penjelasan, lihat posting saya di /unix//a/338121/117599 .

phk
sumber