Procesamiento de texto: une cada dos líneas con comas


35

Tengo más de 1000 líneas en un archivo. El archivo comienza de la siguiente manera (se agregaron números de línea):

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

Necesito convertir esto en un archivo, con entradas separadas por comas uniendo cada dos líneas. Los datos finales deberían verse como

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

Lo que estaba intentando era: tratar de escribir un script de shell y luego echocon una coma en el medio. Pero supongo que una línea más simple y efectiva haría el trabajo aquí puede estar en sed/ awk.

¿Algunas ideas?


@ l0b0 Ha editado a cabo la observación de la OP que los números de línea son "allí sólo por la explicación" ...
jasonwryan

@jasonwryan Lo siento, pensé que las líneas estaban allí para una explicación. Error de análisis en la línea 0.
l0b0

Respuestas:


39

Simplemente use cat(si le gustan los gatos ;-)) y paste:

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

Explicación: pastelee de varios archivos y pega las líneas correspondientes (línea 1 del primer archivo con la línea 1 del segundo archivo, etc.):

paste file1 file2 ...

En lugar de un nombre de archivo, podemos usar -(guión). pastetoma la primera línea del archivo1 (que es stdin). Luego, quiere leer la primera línea del archivo2 (que también es estándar). Sin embargo, dado que la primera línea de stdin ya se leyó y procesó, lo que ahora espera en la secuencia de entrada es la segunda línea de stdin, que pastefelizmente se pega a la primera. La -dopción establece que el delimitador sea una coma en lugar de una pestaña.

Alternativamente, hacer

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

PD Sí, uno puede simplificar lo anterior a

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

o

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

que tiene la ventaja de no usar cat.

Sin embargo, no utilicé este idioma a propósito , por razones de claridad, es menos detallado y me gusta cat(LOS GATOS SON AGRADABLES). Así que por favor no edites.

Alternativamente, si prefiere pegar a gatos (pegar es el comando para concatenar archivos horizontalmente, mientras que gato los concatena verticalmente), puede usar:

paste file.in | paste -d, - -

Solo por mencionarlo de nuevo. Los números de línea no son parte del archivo :)
mtk

El paste comando funciona perfectamente, ¿puede darnos un poco más de explicación? Los guiones ???
mtk

2
Los guiones significan "leer de stdin". Si se repite la misma fuente de entrada, pegar sabe leer varias veces por fila de salida.
dubiousjim

@sch: edición genial, no lo tocaré :-)
Enero

1
Con respecto a su catargumento. No sed "N;s/\n/,/" file.in > file.outfunciona
Bernhard

8

En caso de que alguien que aterriza aquí esté buscando combinar todas las líneas en un revestimiento CSV, intente

cat file | tr '\n' ','

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

Usando sed, une (N) cada 2 líneas y reemplaza la nueva línea (\ n) con ",".


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

También tenga en cuenta que debido a que simplemente estamos reemplazando un carácter con otro (cada línea nueva con una coma), podemos trabajar en el archivo de entrada en su lugar:

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

(pero tenga en cuenta que podría no funcionar en sistemas que no sean Unix que tengan terminadores CRLF (como los de Microsoft) que algunos POSIX emulados pastepodrían tratar de una manera que no sea Unix)


¿Qué hace eso 1aquí 1<>? ¿Es eso un error tipográfico?
αғsнιη

@ αғsнιη, mira esto
iruvar

@iruvar gracias
αғsнιη

2

Aquí hay una línea (aunque potencialmente millones de comandos-run-er) usando Bash puro:

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

Utilizo una subshell (la parántesis) para no tener que almacenar y restaurar IFS. De lo contrario, qué debería hacer para no estropear el entorno de los usuarios en caso de que se obtenga la fuente. La alternativa sería pasar que los nuevos IFS sólo para readque en IFS= read -r name, IFS= read -r code.

El hecho de que todos los comandos del bucle estén integrados en el shell hace que su rendimiento sea aceptable y es incluso más rápido que las otras soluciones para archivos pequeños. Pero muchas personas lo considerarían una mala práctica y uno debe tener cuidado al generalizarlo a cualquier otra cosa.


en general, por usar subcapas para localizar cambios en el entorno. Pero en este caso no es necesario: puede hacerlo while IFS='\n' read -r name; do IFS='\n' read -r code ... done < file.in, lo cual es un idioma que a menudo veo en los scripts de shell. El -rindicador a readsignifica "interpretar el carácter '\' seguido del carácter 'n' en la secuencia estándar como dos caracteres, en lugar de como una nueva línea". Podría decirse que puede ser más estético crear la subshell como lo hace que repetirla IFS='\n'.
dubiousjim

@dubiousjim: -rMejoraron técnicamente la solución. ¡Excelente! No soy fanático de la idea de pasar un cambio IFSdos veces. Si hubiera usado una lectura, super agradable, pero no dos veces. Por supuesto que es una cuestión de opinión . El uso de una subshell es un poco mayor que el conocimiento general de Bash, diría, por lo que mucha gente tendrá problemas para comprender su propósito. Eso es algo malo
Borrado el

2

Para el conjunto completo de respuestas, una posible awksolución puede ser:

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

@downvoter: ¿Qué hay de malo en mi respuesta para merecer un voto negativo? ¿Cómo puede ser mejorado?
Bernhard

Tal vez porque el vago printf? Fallará en el raro caso cuando el nombre de una estación contiene un especificador de formato. (Consulte pastebin.com/wgxFttrJ para ver un ejemplo). Pero esto es solo una suposición, el voto negativo no es mío.
manatwork

1

Canoso viejo castaño de un awkidioma

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'es más corto y más idiomático
cuonglm

@cuonglm, lo dudo. En este caso, sigue siendo una frase a pesar de printla intención y es clara. 1es tan claro para las personas mayores awkcomo yo, pero prefieroprint
iruvar

Esta fue la primera solución simple que encontré que era fácilmente configurable en más de 2 líneas. Luché sedpor un tiempo antes de buscar, pero awkhice que combinar cada 4 líneas sea más fácil. Me salvó un viaje a la $EDITOR!
opello

0

Posible con perl también,

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


0

Por ejemplo:

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

Salida: (nota: xargs -L number_of_columnsfunciona bien con la mayoría de las columnas, no solo cada dos líneas)

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

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.