Transformer la liste en une seule ligne avec délimiteur


17

Je dois prendre une liste (charges) d'adresses IP dans ce format:

 134.27.128.0
 111.245.48.0
 109.21.244.0

et les transformer en ce format avec un tuyau entre les deux (IP composées)

134.27.128.0 | 111.245.48.0 | 109.21.244.0 | 103.22.200.0/22

Je pense que c'est une commande find and replace comme sedmais je ne peux pas la faire fonctionner.


3
Vous voulez simplement trinsérer des nouvelles lignes dans des |tuyaux? Comme <ipfile tr \\n \| >outfile?
mikeserv

L'espace autour est-il |nécessaire?
cuonglm

2
@uselesslinuxman - non. Vous auriez besoin de la redirection d'entrée <. Alors <mydoc tr \\n \| >mydoc2. Mais cela ne vous donnera pas les espaces. Pour ceux-là, la solution la plus rapide est paste -d' | ' mydoc /dev/null /dev/null >mydoc2
probablement

1
@mikeserv: Je ne pense pas que cela fonctionnera. pasteécrit des lignes correspondant à chaque fichier. Sans -s, vous récupérerez le nombre de lignes que vous avez dans le fichier.
cuonglm

2
@ val0x00ff: Je vous invite à lire unix.stackexchange.com/q/169716/38906
cuonglm

Réponses:


16

Avec sed, basé sur Célèbres Sed one-liners Expliqué Partie I: : 39. Append une ligne à l'autre si elle se termine par une barre oblique inverse « \ » (sauf ici on fait abstraction de la part de la barre oblique inverse, et remplaçons les \nnouvelles lignes avec la |séparateur requis ):

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

devrait produire en mydoc2

134.27.128.0 |  111.245.48.0 |  109.21.244.0

@don_crissti désolé c'était un type - corrigé, merci
steeldriver

Malheureusement, cela ne fonctionne pas dans la pratique. Du moins, pas pour des flux illimités. Lorsque vous faites cela, vous devez avaler l'intégralité de votre entrée une ligne à la fois et vous ne pouvez pas en écrire un seul octet en sortie avant d'avoir tout digéré - le tout transformé en une seule ligne. Il est difficile à manier et enclin à segfault.
mikeserv

Un million d'IP est <16M, vous auriez besoin d'une liste terriblement longue pour faire exploser les limites ici. L'utilisation de la recherche pour la détection eof est plus problématique, tout comme cela exécutera O (N ^ 2) sur la taille du fichier d'entrée. sed 'H;1h;$!d;x;s/\n/ | /g'est linéaire.
1er

@jthill - POSIX ne garantit qu'un sedespace modèle de 8K; c'est beaucoup moins de 16 millions.
mikeserv

9

J'étais curieux de voir comment certains d'entre eux (+ quelques alternatives) fonctionnent en termes de vitesse avec un fichier assez volumineux ( 163MiBun IPpar ligne, ~ 13 millions de lignes):

wc -l < iplist
13144256

Résultats (avec sync; echo 3 > /proc/sys/vm/drop_cachesaprès chaque commande; j'ai répété les tests - dans l'ordre inverse - après quelques heures mais les différences étaient négligeables; notez également que j'utilise gnu sed):

tournevis :
très lent. Avorté après deux minutes d'attente ... donc pas de résultat pour celui-ci.

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

et

val0x00ff :

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

real    3m4.321s

ce qui signifie 184.321s. Sans surprise, c'est 200 fois plus lent que la solution de mikeserv .


Voici d'autres façons d'
utiliser 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

une combinaison tête + pâte + tr + chat:

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

real    0m0.991s

Si vous en avez GNU coreutilset si votre liste d'adresses IP n'est pas vraiment énorme (disons jusqu'à 50000 adresses IP), vous pouvez également le faire avec 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

par exemple pour un fichier de 6 lignes:

134.28.128.0
111.245.28.0
109.245.24.0
128.27.88.0
122.245.48.0
103.44.204.0

la commande:

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

les sorties:

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 - pourriez-vous également ajouter la suggestion dans la question de @ val0x00ff pour la while ... readboucle? Je suis curieux de voir à quoi se traduit 163k read()et write()appels dans une référence. Excellente réponse, au fait.
mikeserv

1
@mikeserv - pas de problème, je vais le faire (ce sera très lent cependant).
don_crissti

C'est un lien vraiment cool. J'aime particulièrement que l'auteur propose également un lien vers un benchmark similaire de 6 ans. Avez-vous remarqué que cela sedsemble avoir amélioré sa position à ce moment-là (et n'a probablement apporté que très peu de modifications à son moteur d'expression régulière), mais grepsemble avoir considérablement pris du retard dans ses performances (en particulier pour les lignes plus longues) ? Je me demande si les perlajouts à son moteur ont une incidence sur ces résultats ... C'est aussi soigné qui dashn'est pas abyssal . L' bashici serait probablement beaucoup plus lent avec le courant IFS=ajouté.
mikeserv

hmm ... ce lien est encore un autre indicateur fort que j'ai vraiment besoin d'attacher et d'apprendre le C pour que je puisse enfin commencer à l'utiliser lexcorrectement.
mikeserv

8

Vous pouvez utiliser awk :

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

ORS=' | 'définissez le séparateur d'enregistrement de sortie sur au ' | 'lieu de la nouvelle ligne.

ou éditez sur place avec perl:

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

Merci mec. Je viens d'apprendre comment ça pastemarche. très appréciée.
mikeserv

@mikeserv: Je vous en prie. comme don_crissti l'a montré dans son benchmark, la pastesolution est la plus rapide.
cuonglm

La sortie ne se termine pas par une nouvelle ligne. Vous devrez peut-être remplacer l' ORS=""intérieur du ENDbloc par ORS="\n"pour qu'il le fasse.
phk

4

J'ai donc eu tout faux - et cette question m'a beaucoup appris paste. Comme cuonglm le note correctement, à moins que vous ne disposiez d' pasteun fichier in en -serial, vous finirez toujours avec la dernière ligne \nélectronique de votre liste d'infile ajoutée à la sortie telle qu'elle est écrite. Je me trompais en croyant que le paste -scomportement était son mode par défaut - et c'est une idée fausse qui, apparemment, busybox pasteétait heureuse de renforcer. La commande suivante fonctionne comme annoncé avec busybox:

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

Cependant, cela ne fonctionne pas selon les spécifications. Une mise en œuvre correctement pasteajouterait toujours une ligne de \nqueue pour chaque séquence écrite. Pourtant, ce n'est pas grave après tout:

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

@don_crissti - dangit. tablette stupide. Je suppose que la chose la plus évidente à faire est deux pâtes.
mikeserv

1
Eh bien, j'avais prà l'esprit, mais apparemment, il s'essouffle avec d'énormes fichiers d'entrée, donc je ne pouvais pas réellement tester la vitesse, mais avec des fichiers de longueur raisonnable, cela fonctionne bien. Votre solution est de loin la plus rapide (pas de surprise - pastec'est vraiment rapide), voir mon article.
don_crissti

4

one-liner avec tr et sed:

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

Pourquoi supprimer 2 tuyaux de fuite? Il n'y aura que 2 à la fin si l'entrée s'est terminée par une ligne vierge (deux nouvelles lignes).
JigglyNaga

3

Utiliser vim:

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

Explication:

-n désactiver le fichier d'échange

-u NONE est utilisé pour ignorer toutes les initialisations.

-c {command} exécuter des commandes après la lecture du fichier.

1,$-1s/\n/ | /gest s/\n/ | /g(remplacer la nouvelle ligne par l'espace du tube espace) pour la plage 1,$-1s(1ère ligne à la dernière ligne - 1)

wq! forcer l'écriture et quitter


Remarque:

Selon la taille réelle de votre fichier, cela peut être une mauvaise idée.


1
Je vous remercie tous, car pratiquement chacune de ces commandes fonctionne pour ce que je dois accomplir. Je sais où venir maintenant si (quand) je suis de nouveau coincé. Merci
uselesslinuxman

2

Par python.

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

avant printétait très important.


2

En voici un autre utilisant xxd

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

2

Par souci d'exhaustivité, voici une autre awksolution basée sur celle-ci, celle-ci n'utilise pas du ORStout:

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

Pour une explication, voir mon article sur /unix//a/338121/117599 .

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.