Traitement de texte - joindre toutes les deux lignes avec des virgules


35

J'ai plus de 1000 lignes dans un fichier. Le fichier commence comme suit (numéros de ligne ajoutés):

Station Name
Station Code
A N DEV NAGAR
ACND
ABHAIPUR
AHA
ABOHAR
ABS
ABU ROAD
ABR

J'ai besoin de convertir cela en un fichier, avec des entrées séparées par des virgules en joignant toutes les deux lignes. Les données finales devraient ressembler à

Station Name,Station Code
A N DEV NAGAR,ACND
ABHAIPUR,AHA
ABOHAR,ABS
ABU ROAD,ABR
...

Ce que j’essayais, c’est d’essayer d’écrire un script shell puis de echoles séparer par une virgule. Mais j'imagine qu'une solution simple et efficace ferait l'affaire ici sed/ awk.

Des idées?


@ l0b0 Vous avez supprimé la remarque de l'OP selon laquelle les numéros de ligne ne sont "que pour expliquer" ...
jasonwryan

@ jasonwryan Désolé, je pensais que les lignes étaient là pour des explications. Erreur d'
analyse

Réponses:


39

Utilisez simplement cat(si vous aimez les chats ;-)) et paste:

cat file.in | paste -d, - - > file.out

Explication: pastelit dans un certain nombre de fichiers et colle ensemble les lignes correspondantes (ligne 1 du premier fichier avec ligne 1 du deuxième fichier, etc.):

paste file1 file2 ...

Au lieu d'un nom de fichier, nous pouvons utiliser -(tiret). pasteprend la première ligne de file1 (qui est stdin). Ensuite, il veut lire la première ligne de file2 (qui est aussi stdin). Cependant, puisque la première ligne de stdin a déjà été lue et traitée, ce qui attend maintenant sur le flux d'entrée est la deuxième ligne de stdin, qui pastese colle heureusement à la première. L' -doption définit le délimiteur comme une virgule plutôt qu'un onglet.

Sinon, faire

cat file.in | sed "N;s/\n/,/" > file.out

PS Oui, on peut simplifier ce qui précède pour

< file.in sed "N;s/\n/,/" > file.out

ou

< file.in paste -d, - - > file.out

qui a l'avantage de ne pas utiliser cat.

Cependant, je n'ai pas utilisé cet idiome exprès , pour des raisons de clarté - il est moins bavard et j'aime bien cat(CATS ARE NICE). Alors s'il vous plaît ne pas éditer.

Alternativement, si vous préférez coller aux chats (coller est la commande pour concaténer des fichiers horizontalement, tandis que cat les concaténer verticalement), vous pouvez utiliser:

paste file.in | paste -d, - -

Juste pour le mentionner à nouveau. Les numéros de ligne ne font pas partie du fichier :)
mtk

La paste commande fonctionne parfaitement, pouvez-vous s'il vous plaît donner un peu plus d'explications à ce sujet. Les traits d'union ???
MTK

2
Les traits d'union signifient "lu à partir de stdin". Si la même source d'entrée est répétée, paste sait la lire plusieurs fois par ligne de sortie.
dubiousjim

@sch: cool, je n'y toucherai pas :-)
janvier

1
En ce qui concerne votre catargument. Ne sed "N;s/\n/,/" file.in > file.outfonctionne pas?
Bernhard

8

Si quelqu'un qui atterrit ici cherche à combiner toutes les lignes dans un liner CSV, essayez

cat file | tr '\n' ','

3
sed 'N;s/\n/,/' file

Avec sed, joignez (N) toutes les 2 lignes et remplacez la nouvelle ligne (\ n) par ",".


3
paste -sd ',\n' file.in > file.out

Notez également que, comme nous remplaçons simplement un caractère par un autre (chaque nouvelle ligne par une virgule), nous pouvons travailler sur le fichier d'entrée à la place:

paste -sd ',\n' file.in 1<> file.in

(mais attention, il est possible que cela ne fonctionne pas sur des systèmes non-Unix dotés de terminateurs CRLF (comme ceux de Microsoft) que certains POSIX émulés pastepourraient traiter de manière non-Unix)


Qu'est-ce que ça 1fait ici 1<>? est-ce une faute de frappe?
dimanche

@ αғsнιη, voyez ceci
iruvar le

@iruvar je vous remercie
lundi

2

Voici un one-liner (bien que potentiellement des millions de commandes) utilisant Bash pur:

(IFS=; while read -r name; do read -r code; printf '%s\n" "$name,$code"; done < file.in) > file.out

J'utilise un sous-shell (la parenthèse) pour ne pas avoir à stocker ni restaurer IFS. Ce que l’autre devrait faire pour ne pas gâcher l’environnement des utilisateurs au cas où la source serait source L'alternative serait de passer que les nouvelles IFS uniquement readcomme IFS= read -r name, IFS= read -r code.

Le fait que toutes les commandes de la boucle soient intégrées au shell rend ses performances acceptables et est même plus rapide que les autres solutions pour les petits fichiers. Mais beaucoup de gens considéreraient cela comme une mauvaise pratique et il faut être prudent lorsqu’on la généralise à autre chose.


généralement en utilisant des sous-shell pour localiser les changements d’environnement. Mais dans ce cas, ce n'est pas nécessaire: vous pouvez le faire while IFS='\n' read -r name; do IFS='\n' read -r code ... done < file.in, ce qui est un idiome que je vois souvent dans les scripts shell. Le -rdrapeau readsignifie "interprète le caractère '\' suivi du caractère 'n' dans le flux stdin comme deux caractères plutôt que comme une nouvelle ligne." On peut soutenir qu'il peut être plus esthétique de créer le sous-shell que de le répéter IFS='\n'.
dubiousjim

@dubiousjim: Le -ramélioré techniquement la solution. Génial! Je ne suis pas fan de l'idée de passer un IFSdouble changé . Si j'avais utilisé une lecture, super sympa, mais pas deux fois. Bien sûr, c'est une question d' opinion . L'utilisation d'un sous-shell est un peu plus complexe que la connaissance générale de Bash, de sorte que beaucoup de gens auront du mal à comprendre son objectif. C'est une mauvaise chose.
Supprimé

2

Pour l’ensemble complet de réponses, une awksolution possible pourrait être:

awk 'NR%2==1 {printf $0","} NR%2==0 { print $0}' *file*

@ downvoter: Qu'est-ce qui ne va pas dans ma réponse pour mériter un vote négatif? Comment peut-il être amélioré?
Bernhard

Peut-être parce que les paresseux printf? Echouera dans les rares cas où un nom de station contient un spécificateur de format. (Voir pastebin.com/wgxFttrJ pour un exemple.) Mais ceci est juste une supposition, le vote par opposition ne vient pas de moi.
Manatwork

1

Hoary vieux marron d'un awkidiome

awk '{ORS=NR%2?",":"\n";print}' file
Station Name,Station Code
A N DEV NAGAR,ACND
ABHAIPUR,AHA
ABOHAR,ABS
ABU ROAD,ABR

awk '{ORS=NR%2?",":"\n"};1'est plus court et plus idiome
cuonglm

@cuonglm, j'en doute. Dans ce cas, il s’agit toujours d’un one-liner malgré le printet l’intention est claire. 1C’est tout aussi clair pour les anciens awkcomme moi mais je préfèreprint
iruvar

Il s’agit de la première solution simple que j’ai trouvée et qui était facilement configurable à plus de 2 lignes. Je me suis battu sedpendant un moment avant de chercher, mais je pouvais awkcombiner toutes les 4 lignes plus facilement. M'a sauvé un voyage à la $EDITOR!
opello

0

Possible aussi avec perl,

perl -pe 's/^\d+\.\s+//;$.&1?chomp:print","' file


0

Par exemple:

seq 0 70 | xargs -L 2 | sed 's/ /,/g'

Sortie: (note: xargs -L number_of_columnsfonctionne bien avec la plupart des colonnes et pas seulement toutes les deux lignes)

0,1
2,3
4,5
6,7
8,9
10,11
12,13
14,15
16,17
18,19
20,21
22,23
24,25
26,27
28,29
30,31
32,33
34,35
36,37
38,39
40,41
42,43
44,45
46,47
48,49
50,51
52,53
54,55
56,57
58,59
60,61
62,63
64,65
66,67
68,69
70

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.