Zamień listę w pojedynczy wiersz z separatorem

17

Muszę wziąć listę (obciążenia) adresów IP w tym formacie:

 134.27.128.0
 111.245.48.0
 109.21.244.0

i zamień je w ten format, umieszczając między nimi potok (utworzone adresy IP)

134.27.128.0 | 111.245.48.0 | 109.21.244.0 | 103.22.200.0/22

Myślę, że jest to polecenie znajdowania i zastępowania, sedale nie mogę go uruchomić.

uselesslinuxman
źródło
3
Chcesz tylko trpołączyć nowe wiersze w |rury? Jak <ipfile tr \\n \| >outfile?
mikeserv
Czy |wymagana jest przestrzeń wokół ?
cuonglm
2
@uselesslinuxman - nie. Potrzebujesz przekierowania wejściowego <. Tak <mydoc tr \\n \| >mydoc2. Ale to nie zapewni ci przestrzeni. Dla tych, prawdopodobnie najszybszym rozwiązaniem jestpaste -d' | ' mydoc /dev/null /dev/null >mydoc2
mikeserv
1
@mikeserv: Nie sądzę, żeby to zadziałało. pastezapisuje wiersze odpowiadające każdemu plikowi. Bez -stego otrzymasz liczbę wierszy, które masz w pliku.
cuonglm
2
@ val0x00ff: Zapraszam do lektury unix.stackexchange.com/q/169716/38906
cuonglm

Odpowiedzi:

16

Korzystanie sed, na podstawie Znani sed jednej wkładki Poradnik, część I: : 39. Dołącz linia do następnej, jeśli kończy się backslash „\” (z wyjątkiem tutaj ignorujemy część o ukośnik i załóż \nnowe linie Europejska wymagany |separator):

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

powinien produkować w mydoc2

134.27.128.0 |  111.245.48.0 |  109.21.244.0
steeldriver
źródło
@don_crissti przepraszam, że to był typ - poprawiony, dzięki
steeldriver
Niestety nie działa to w praktyce. Przynajmniej nie dla nieograniczonej liczby strumieni. Kiedy to zrobisz, musisz połknąć cały swój wiersz na raz i nie możesz zapisać ani jednego bajtu, aby go wyprowadzić, dopóki go nie przetrawisz - wszystko to przekształci się w jedną linię. Jest nieporęczny i podatny na awarie.
mikeserv
Milion adresów IP to <16 mln, potrzebna byłaby strasznie duża lista, aby przekroczyć limity tutaj. Użycie wykrywania eof jest bardziej problematyczne, ponieważ spowoduje to uruchomienie O (N ^ 2) na rozmiarze pliku wejściowego. sed 'H;1h;$!d;x;s/\n/ | /g'jest liniowy.
jthill
@jthill - POSIX gwarantuje jedynie sedprzestrzeń wzorów 8K; to o wiele mniej niż 16 milionów.
mikeserv
9

Byłem ciekawy, jak niektóre z tych (+ niektóre alternatywy) działają szybko z dość dużym plikiem ( 163MiBjeden IPna linię, ~ 13 milionów linii):

wc -l < iplist
13144256

Wyniki ( sync; echo 3 > /proc/sys/vm/drop_cachespo każdym poleceniu; powtórzyłem testy - w odwrotnej kolejności - po kilku godzinach, ale różnice były znikome; zauważ też, że używam gnu sed):

steeldriver :
bardzo wolny. Przerwany po dwóch minutach oczekiwania ... więc nie ma dla niego żadnego wyniku.

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

i

val0x00ff :

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

real    3m4.321s

które to środki 184.321s. Nic dziwnego, że jest to 200 razy wolniej niż rozwiązanie mikeserv .


Oto kilka innych sposobów korzystania z
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

połączenie głowy + pasty + tr + kota:

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

real    0m0.991s

Jeśli masz GNU coreutilsi jeśli twoja lista adresów IP nie jest naprawdę duża (powiedzmy do 50000 adresów IP), możesz to również zrobić za pomocą pr:

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

gdzie

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

np. dla pliku 6-liniowego:

134.28.128.0
111.245.28.0
109.245.24.0
128.27.88.0
122.245.48.0
103.44.204.0

Komenda:

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

wyjścia:

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
źródło
don - czy możesz również dodać sugestię do pytania przez @ val0x00ff dla while ... readpętli? Jestem ciekawy, co przekłada się na 163k read()i write()połączenia. Nawiasem mówiąc, świetna odpowiedź.
mikeserv
1
@mikeserv - nie ma problemu, zrobię to (będzie to jednak naprawdę powolne ).
don_crissti
To naprawdę fajny link. Szczególnie podoba mi się to, że autor oferuje tam link do podobnego 6-letniego testu porównawczego. Czy zauważyłeś, że sedw tym czasie poprawiła się jego pozycja (i prawdopodobnie wprowadzono tylko kilka zmian w jego silniku regexp), alegrep wydaje się, że znacznie spadła pod względem wydajności (szczególnie w przypadku dłuższych linii) ? Zastanawiam się, czy perldodatki do silnika mają jakikolwiek wpływ na te wyniki ... To też dashjest fajne, że nie jest beznadziejne . bashTutaj będzie prawdopodobnie znacznie wolniej w / wspólna IFS=poprzedzany.
mikeserv
hmm ... ten link jest kolejnym silnym wskaźnikiem, który naprawdę muszę zapiąć i nauczyć się C, aby w końcu móc zacząć lexprawidłowo używać .
mikeserv
8

Możesz użyć awk :

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

ORS=' | 'ustaw separator rekordów wyjściowych na ' | 'zamiast nowego wiersza.

lub edytuj w miejscu za pomocą perl:

perl -pe 's/\n/ | / unless eof' file
Cuonglm
źródło
dzięki. Właśnie dowiedziałem się, jak pastedziała. bardzo mile widziane.
mikeserv
@mikeserv: Nie ma za co. jak don_crissti pokazał w swoim teście, pasterozwiązanie jest najszybsze.
cuonglm
Wyjście nie kończy się na nowej linii. Może być konieczne zastąpienie ORS=""wewnątrz ENDbloku ORS="\n"tak, aby to zrobiło.
phk
4

Więc źle to wszystko zrozumiałem - i to pytanie wiele mnie nauczyło paste. Jak słusznie zauważa cuonglm, chyba że jesteś pastew erialnym pliku -s, zawsze \nskończysz w / ewline z listy infile dołączanej do wyjścia w miarę jego zapisywania. Myliłem się w przekonaniu, że paste -szachowanie było jego domyślnym trybem - i jest to nieporozumienie, które, jak się wydaje, z busybox pasteprzyjemnością umacnia. Następujące polecenie działa jak w reklamie busybox:

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

Jednak nie działa zgodnie ze specyfikacją. Prawidłowo zaimplementowana pastenadal \ndołączałaby końcową ewline dla każdej zapisanej sekwencji. Mimo wszystko to nie jest wielka sprawa:

paste -d\  - infile - </dev/null | paste -sd\| - >outfile
mikeserv
źródło
@don_crissti - dangit. głupia tabletka. Myślę, że oczywistą rzeczą do zrobienia są dwie pasty.
mikeserv
1
Cóż, miałem prna myśli, ale najwyraźniej nie ma pary z dużymi plikami wejściowymi, więc nie mogłem faktycznie przetestować prędkości, ale przy plikach o rozsądnej długości działa OK. Twoje rozwiązanie jest zdecydowanie najszybsze (nic dziwnego - pastejest naprawdę szybkie), patrz mój post.
don_crissti
4

jednowarstwowa z tr i sed:

cat file | tr '\n' '|' | sed 's/||$/\n/'
134.27.128.0|111.245.48.0|109.21.244.0
użytkownik5337995
źródło
Po co usuwać 2 końcowe rury? Na końcu będą tylko 2, jeśli dane wejściowe zakończą się pustą linią (dwie nowe linie).
JigglyNaga,
3

Wykorzystaj vim:

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

Wyjaśnienie:

-n wyłącz plik wymiany

-u NONE służy do pominięcia wszystkich inicjalizacji.

-c {command} wykonać polecenia po odczytaniu pliku.

1,$-1s/\n/ | /gto s/\n/ | /g(zamień znak nowej linii spacją odstępu) dla zakresu 1,$-1s(od 1. linii do ostatniej linii - 1)

wq! wymuś pisanie i wyjście


Uwaga:

W zależności od tego, jak duży jest Twój plik, może to być zły pomysł.

FloHimself
źródło
1
Dziękuję wszystkim, ponieważ w zasadzie prawie każde z tych poleceń działa na rzecz tego, co muszę osiągnąć. Wiem, gdzie teraz przyjść, jeśli (kiedy) utknę ponownie. Dzięki
bezużytecznylinux
2

Poprzez python.

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

przestrzenie wcześniej printbyły bardzo ważne.

Avinash Raj
źródło
2

Oto inny za pomocą xxd

xxd -c1 -ps data | sed '$!s/0a/207c20/' | xxd -r -ps
FloHimself
źródło
2

Dla kompletności, oto inne awkrozwiązanie oparte na tym, które ORSw ogóle nie używa :

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

Aby uzyskać wyjaśnienie, zobacz mój post na /unix//a/338121/117599 .

phk
źródło