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

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но я не могу заставить ее работать.

uselesslinuxman
источник
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
steeldriver
источник
@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

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

{ 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
don_crissti
источник
Дон - не могли бы вы также добавить предложение в вопросе по @ val0x00ff для while ... readцикла? Мне любопытно посмотреть, что означает 163k read()и write()звонки в тесте. Отличный ответ, кстати.
mikeserv
1
@mikeserv - нет проблем, я сделаю это ( хотя это будет очень медленно ).
don_crissti
Это действительно крутая ссылка. Мне особенно нравится, что автор также предлагает ссылку на аналогичный 6-летний тест. Вы заметили, что, sedкажется, улучшилось его положение в то время (и, вероятно, было только несколько изменений в его движке регулярных выражений), но, grepпохоже, резко отстали в его производительности (особенно для более длинных линий) ? Интересно , если perlдополнения к его двигателю имеют какое - либо отношение этих результатов ... Это также аккуратное , что dashне бездонные . bashЗдесь, вероятно , будет гораздо медленнее , ж / общий IFS=предваряется.
mikeserv
хм ... эта ссылка является еще одним сильным индикатором того, что мне действительно нужно пристегнуться и выучить C, чтобы я мог наконец начать 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
cuonglm
источник
Спасибо чувак. Я только что узнал, как pasteработает. очень признателен.
mikeserv
@mikeserv: Пожалуйста. Как показал don_crissti в своем тесте, pasteрешение является самым быстрым.
Cuonglm
Вывод не заканчивается новой строкой. Возможно, вам придется заменить ORS=""внутри ENDблока, ORS="\n"чтобы он сделал.
PhK
4

Так что я все понял неправильно - и этот вопрос многому меня научил paste. Как правильно замечает cuonglm, если вы не входите pasteв файл в -serial, вы всегда будете \nполучать последнюю строку из вашего infile-списка, добавляемую к выводу в том виде, в котором она написана. Я ошибался, полагая, что paste -sповедение было его режимом по умолчанию - и это заблуждение, которое, по-видимому, busybox pasteбыл рад подкрепить. Следующая команда работает как объявлено с busybox:

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

Это не работает в соответствии со спецификацией, хотя. Правильно реализованное действие pasteвсе равно добавит завершающий \newline для каждой написанной последовательности. Тем не менее, в этом нет ничего особенного:

paste -d\  - infile - </dev/null | paste -sd\| - >outfile
mikeserv
источник
@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
user5337995
источник
Зачем удалять 2 висячие трубы? В конце будет только 2, если ввод закончился пустой строкой (две строки).
JigglyNaga
3

Использовать vim:

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

Объяснение:

-n отключить файл подкачки

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

-c {command} выполнять команды после прочтения файла.

1,$-1s/\n/ | /gis s/\n/ | /g(заменить символ новой строки пробелом пробела) для диапазона 1,$-1s(с 1-й строки до последней строки - 1)

wq! заставить писать и выйти


Замечания:

В зависимости от того, насколько большой ваш файл на самом деле, это может быть плохой идеей.

FloHimself
источник
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
FloHimself
источник
2

Для полноты картины, вот еще одно awkрешение, основанное на этом, которое вообще не использует ORS:

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

Для объяснения см. Мой пост на /unix//a/338121/117599 .

PHK
источник