Transformar lista em uma única linha com delimitador

17

Eu tenho que ter uma lista (cargas) de endereços IP neste formato:

 134.27.128.0
 111.245.48.0
 109.21.244.0

e transformá-los nesse formato com um tubo intermediário (IPs compostos)

134.27.128.0 | 111.245.48.0 | 109.21.244.0 | 103.22.200.0/22

Eu acho que é um comando de localização e substituição, sedmas não consigo fazê-lo funcionar.

uselesslinuxman
fonte
3
Você só quer trincluir novas linhas em |pipes? Gosta <ipfile tr \\n \| >outfile?
precisa saber é o seguinte
O espaço ao redor é |necessário?
precisa saber é
2
@uselesslinuxman - não. Você precisaria do redirecionamento de entrada <. Então <mydoc tr \\n \| >mydoc2. Mas isso não vai lhe dar espaço. Para aqueles, provavelmente a solução mais rápida épaste -d' | ' mydoc /dev/null /dev/null >mydoc2
mikeserv
1
@ MikeServ: Eu não acho que vai funcionar. pasteescreve linhas correspondentes a cada arquivo. Sem -s, você receberá de volta o número de linhas que possui no arquivo.
precisa saber é
2
@ val0x00ff: Convido você a ler unix.stackexchange.com/q/169716/38906
cuonglm

Respostas:

16

Usando sed, com base em famosos Sed one-liners explicou, Parte I: : 39. Anexar uma linha para a próxima se ele termina com uma barra invertida "\" (exceto aqui vamos ignorar a parte sobre a barra invertida, e substituir as \nquebras de linha com o |separador necessário ):

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

deve produzir em mydoc2

134.27.128.0 |  111.245.48.0 |  109.21.244.0
chave de aço
fonte
@don_crissti pesaroso que era um tipo - corrigido, graças
steeldriver
Infelizmente, isso não funciona na prática. Pelo menos, não para fluxos ilimitados. Ao fazer isso, é necessário engolir toda a sua entrada uma linha de cada vez e não é possível gravar nem um único byte dela na saída até que você tenha digerido tudo - tudo isso transformado em uma única linha. É pesado e propenso a segfault.
precisa saber é o seguinte
Um milhão de IPs é <16M, você precisaria de uma lista muito grande para estourar limites aqui. Usar a pesquisa para detecção de eof é mais problemático, pois é executado O (N ^ 2) no tamanho do arquivo de entrada. sed 'H;1h;$!d;x;s/\n/ | /g'é linear.
jthill
@jthill - POSIX garante apenas um sedespaço padrão de 8K; isso é muito menos que 16 milhões.
precisa saber é o seguinte
9

Fiquei curioso para ver como algumas dessas (+ algumas alternativas) funcionam em velocidade com um arquivo bastante grande ( 163MiB, um IPpor linha, ~ 13 milhões de linhas):

wc -l < iplist
13144256

Resultados (com sync; echo 3 > /proc/sys/vm/drop_caches após cada comando; repeti os testes - em ordem inversa - após algumas horas, mas as diferenças eram insignificantes; observe também que estou usando gnu sed):

chave de aço :
muito lento. Abortado após dois minutos de espera ... então não há resultado para este.

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

e

val0x00ff :

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

real    3m4.321s

cujos meios 184.321s. Sem surpresa, isso é 200 vezes mais lento que a solução da mikeserv .


Aqui estão algumas outras maneiras com o
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

uma combinação de cabeça + colar + tr + gato:

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

real    0m0.991s

Se você possui GNU coreutilse se sua lista de IPs não é realmente grande (digamos até 50000 IPs), você também pode fazer isso compr :

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

Onde

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

por exemplo, para um arquivo de 6 linhas:

134.28.128.0
111.245.28.0
109.245.24.0
128.27.88.0
122.245.48.0
103.44.204.0

o comando:

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

saídas:

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
fonte
don - você também pode adicionar a sugestão na pergunta por @ val0x00ff para o while ... readloop? Estou curioso para ver o que 163k read()e write()chamadas traduz em uma referência. Ótima resposta, a propósito.
precisa saber é o seguinte
1
@ MikeServ - não há problema, eu vou fazê-lo (será muito lento embora).
don_crissti
Esse é um link muito legal. Gosto especialmente do autor que também oferece um link para uma referência similar de 6 anos de idade. Você percebe que sedparece ter melhorado sua posição naquele tempo (e provavelmente teve apenas poucas alterações em seu mecanismo de regexp), mas grepparece ter sofrido um atraso dramático no desempenho (especialmente nas linhas mais longas) ? Gostaria de saber se as perladições ao seu motor têm alguma influência sobre esses resultados ... Também é interessante que dashnão seja abismal . O bashaqui provavelmente seria muito mais lento com o comum IFS=precedido.
precisa saber é o seguinte
hmm ... esse link é mais um forte indicador de que realmente preciso me curvar e aprender C para que eu possa finalmente começar a usar lexcorretamente.
precisa saber é o seguinte
8

Você pode usar o awk :

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

ORS=' | 'defina o separador de registro de saída como em ' | 'vez de nova linha.

ou edite no local com perl:

perl -pe 's/\n/ | / unless eof' file
cuonglm
fonte
obrigado cara. Acabei de aprender como pastefunciona. muito apreciado.
precisa saber é o seguinte
@ MikeServ: De nada. como don_crissti mostrou em seu benchmark, a pastesolução é a mais rápida.
precisa saber é
A saída não termina com uma nova linha. Você pode ter que substituir ORS=""dentro do ENDbloco com ORS="\n"para que ele faça.
Php 17/17
4

Então eu entendi tudo errado - e essa pergunta me ensinou muito paste. Como o cuonglm observa corretamente, a menos que você esteja pasteem um arquivo em -ssérie, você sempre terminará com a última \nlinha de ew da sua lista de infile sendo anexada à saída conforme está escrita. Eu estava enganado na crença de que o paste -scomportamento era seu modo padrão - e esse é um equívoco que, aparentemente, busybox pasteficou feliz em reforçar. O comando a seguir funciona como anunciado com busybox:

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

Porém, ele não funciona de acordo com as especificações. Uma implementação correta pasteainda acrescentaria um \newline à direita para cada sequência gravada. Ainda assim, isso não é grande coisa, afinal:

paste -d\  - infile - </dev/null | paste -sd\| - >outfile
mikeserv
fonte
@don_crissti - dangit. tablet estúpido. Eu acho que a coisa mais óbvia a fazer são duas pastas.
precisa saber é o seguinte
1
Bem, eu tinha prem mente, mas aparentemente ele fica sem vapor com grandes arquivos de entrada, então não pude testar a velocidade, mas com arquivos de tamanho razoável, funciona bem. Sua solução é de longe a mais rápida (sem surpresa - pasteé realmente rápida), veja o meu post.
don_crissti
4

uma linha com tr e sed:

cat file | tr '\n' '|' | sed 's/||$/\n/'
134.27.128.0|111.245.48.0|109.21.244.0
user5337995
fonte
Por que excluir 2 tubos à direita? Haverá apenas 2 no final se a entrada terminar com uma linha em branco (duas novas linhas).
JigglyNaga
3

Utilize vim:

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

Explicação:

-n desativar arquivo de troca

-u NONE é usado para pular todas as inicializações.

-c {command} executar comandos após a leitura do arquivo.

1,$-1s/\n/ | /g é s/\n/ | /g (substitua a nova linha pelo espaço do tubo de espaço) para o intervalo1,$-1s (1ª a última linha - 1)

wq! força escrever e sair


Nota:

Dependendo do tamanho do seu arquivo, isso pode ser uma má idéia.

FloHimself
fonte
1
Agradeço a todos, porque basicamente quase todos esses comandos funcionam para o que eu preciso alcançar. Eu sei para onde vir agora se (quando) eu estiver preso novamente. Obrigado
uselesslinuxman
2

Através de python.

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

espaços antes printera muito importante.

Avinash Raj
fonte
2

Aqui está outro usando xxd

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

Por uma questão de completude, aqui está outra awksolução baseada em: esta não está usando a ORS:

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

Para uma explicação, consulte meu post em /unix//a/338121/117599 .

phk
fonte