Перетворіть список в один рядок з роздільником


17

Я повинен взяти список (завантажень) IP-адрес у такому форматі:

 134.27.128.0
 111.245.48.0
 109.21.244.0

і перетворити їх у цей формат за допомогою проміжного каналу (складені IP-адреси)

134.27.128.0 | 111.245.48.0 | 109.21.244.0 | 103.22.200.0/22

Я думаю, що це команда пошуку та заміни на кшталт, sedале я не можу змусити її працювати.


3
Ви просто хочете trдовести нові |трубки до труб? Як <ipfile tr \\n \| >outfile?
mikeserv

Чи потрібний простір навколо |?
cuonglm

2
@uselesslinuxman - ні. Вам знадобиться переспрямування вводу <. Отже <mydoc tr \\n \| >mydoc2. Але це не отримає у вас простору. Для тих, мабуть, найшвидше рішенняpaste -d' | ' mydoc /dev/null /dev/null >mydoc2
mikeserv

1
@mikeserv: Я не думаю, що це спрацює. pasteзаписує рядки, що відповідають кожному файлу. Без цього -sви отримаєте назад кількість рядків у файлі.
cuonglm

2
@ val0x00ff: Я запрошую вас прочитати unix.stackexchange.com/q/169716/38906
cuonglm

Відповіді:


16

Використання СЕД, на основі відомих SED однострочнікі Роз'яснення Частина I: : 39. Append лінія до іншого , якщо вона закінчується слешем «\» ( за винятком тут ми ігноруємо частина про зворотної косої межі, і замінити \nновою рядки з необхідний |роздільник):

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

повинні виробляти в mydoc2

134.27.128.0 |  111.245.48.0 |  109.21.244.0

@don_crissti вибачте, що це був тип - виправлено, спасибі
steeldriver

На жаль, це практично не працює на практиці. Принаймні, не для необмежених потоків. Коли ви робите це, ви повинні проковтнути весь вхідний рядок за один раз і не можете записати навіть один байт його, щоб вивести його, поки ви все це не перекопали - усе це перетворилося в один рядок. Це непростий і схильний до сегментації.
mikeserv

Мільйон IP-адрес становить <16 млн., Вам знадобиться жахливо великий список, щоб підібрати межі тут. Використання пошуку для виявлення eof є більш проблематичним, оскільки це запустить O (N ^ 2) на розмірі вхідного файлу. sed 'H;1h;$!d;x;s/\n/ | /g'є лінійним.
jthill

@jthill - POSIX гарантує лише sedпростір шаблону 8K; це в цілому багато менше, ніж 16 мільйонів.
mikeserv

9

Мені було цікаво побачити, як деякі з цих (+ деякі альтернативи) швидкодіючі з досить великим файлом (163MiB , один IPна рядок, ~ 13 мільйонів рядків):

wc -l < iplist
13144256

Результати ( sync; echo 3 > /proc/sys/vm/drop_cachesпісля кожної команди; я повторював тести - у зворотному порядку - через пару годин, але відмінності були незначними; також зауважте, що я використовую gnu sed):

steeldriver :
Дуже повільно. Аборт після двох хвилин очікування ... так що жодного результату для цього немає.

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

Авінаш Радж :

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

що означає 184.321s. Не дивно, що це в 200 разів повільніше, ніж рішення mikeserv .


Ось деякі інші способи з
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

комбінація голови + паста + tr + кішка:

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

real    0m0.991s

Якщо у вас є GNU coreutilsі якщо ваш список IP-адрес насправді не величезний (скажімо, до 50000 IP-адрес), ви також можете це зробити зpr :

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

де

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

наприклад, для 6-рядкового файлу:

134.28.128.0
111.245.28.0
109.245.24.0
128.27.88.0
122.245.48.0
103.44.204.0

команда:

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

Виходи:

134.28.128.0 | 111.245.28.0 | 109.245.24.0 | 128.27.88.0 | 122.245.48.0 | 103.44.204.0

Дон - ви можете також додати у пропозицію до питання @ val0x00ff для while ... readциклу? Мені цікаво побачити, що в еталоні означає 163k read()та write()дзвінки. Чудова відповідь, до речі.
mikeserv

1
@mikeserv - не проблема, я це зроблю (хоча це буде дуже повільно ).
don_crissti

Це дійсно круте посилання. Мені особливо подобається, що автор пропонує також посилання на аналогічний орієнтир для 6 років. Чи помічаєте ви, що, sedздається, покращило своє становище в той час (і, мабуть, було лише небагато змін у його двигуні regexp), але, grepсхоже, різко відстало в його продуктивності (особливо для довших ліній) ? Цікаво, чи perlдоповнення до його двигуна мають якесь відношення до цих результатів ... Це також акуратно, що dashне є ненормальним . bashТут, ймовірно , буде набагато повільніше , ж / загальний IFS=передує.
mikeserv

хм ... це посилання - це ще один сильний показник того, що мені справді потрібно згорнутись та вивчити С, щоб я нарешті почав lexправильно користуватися.
mikeserv

8

Ви можете використовувати awk :

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

ORS=' | 'встановити вихідний роздільник записів для' | ' замість нового рядка.

або редагувати на місці за допомогою perl:

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

дякую людині. Я щойно дізнався, як pasteпрацює. цінується.
mikeserv

@mikeserv: Ласкаво просимо. як показано в його еталоні don_crissti, pasteрішення є найшвидшим.
cuonglm

Вихід не закінчується новим рядком. Можливо, вам доведеться замінити ORS=""всередині ENDблоку ORS="\n"таким чином, щоб він це зробив .
phk

4

Тож у мене все було не так - і це питання мене багато чому навчило paste. Як правильно зазначає cuonglm, якщо ви не маєте pasteфайл в -serial, ви завжди будете \nзавершувати те, що остання ewline зі списку інфілів додається до виводу, як написано. Я помилився, вважаючи, що paste -sповедінка - це її режим за замовчуванням - і це неправильне уявлення, яке, мабуть, із busybox pasteзадоволенням підкріплювало. Наступна команда працює як рекламоване w / busybox:

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

Однак це не працює відповідно до специфікації. Правильно реалізований pasteвсе-таки додасть \nкінцеву лінію ewline для кожної записаної послідовності. І все-таки, це нічого страшного:

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

@don_crissti - dangit. дурний планшет. Я думаю, очевидно, що потрібно зробити - це дві пасти.
mikeserv

1
Ну, я мав prна увазі, але, мабуть, у нього закінчується пар з величезними вхідними файлами, тому я не міг фактично перевірити швидкість, але з розумними файлами довжини він працює добре. Ви рішення на сьогоднішній день найшвидше (не дивно - pasteце дуже швидко), дивіться мою публікацію.
don_crissti

4

одноводковий з tr і sed:

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

Навіщо видаляти 2 задні труби? У кінці буде лише 2, якщо вхід закінчився порожнім рядком (два нові рядки).
JigglyNaga

3

Використовуйте vim:

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

Пояснення:

-n вимкнути файл свопу

-u NONE використовується для пропуску всіх ініціалізацій.

-c {command} виконувати команди після того, як файл був прочитаний.

1,$-1s/\n/ | /gє s/\n/ | /g(замініть нову лінію пробілом у просторі) для діапазону 1,$-1s(1-й рядок до останнього рядка - 1)

wq! примушуйте писати і кинути


Примітка:

Залежно від того, наскільки насправді великий ваш файл, це може бути поганою ідеєю.


1
Я всім вам вдячний, бо практично кожна з цих команд працює на те, що мені потрібно досягти. Я знаю, куди зараз прийти, якщо (коли) я знову застряг. Спасибі
uselesslinuxman

2

Через пітон.

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

пробіли раніше printбули дуже важливими.


2

Ось ще один із використанням xxd

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

2

Для повноти, ось ще одне awkбазове рішення, це взагалі не використовує ORS:

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

Для пояснення дивіться мою публікацію за адресою /unix//a/338121/117599 .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.