Convertir líneas separadas en una lista separada por comas con entradas entre comillas

15

Tengo los siguientes datos (una lista de paquetes R analizados desde un archivo Rmarkdown), que quiero convertir en una lista que puedo pasar a R para instalar:

d3heatmap
data.table
ggplot2
htmltools
htmlwidgets
metricsgraphics
networkD3
plotly
reshape2
scales
stringr

Quiero convertir la lista en una lista del formulario:

'd3heatmap', 'data.table', 'ggplot2', 'htmltools', 'htmlwidgets', 'metricsgraphics', 'networkD3', 'plotly', 'reshape2', 'scales', 'stringr'

Actualmente tengo una tubería de bash que va del archivo sin procesar a la lista anterior:

grep 'library(' Presentation.Rmd \
| grep -v '#' \
| cut -f2 -d\( \
| tr -d ')'  \
| sort | uniq

Quiero agregar un paso para convertir las nuevas líneas en la lista separada por comas. He intentado agregar tr '\n' '","', que falla. También probé varias de las siguientes respuestas de desbordamiento de pila, que también fallan:

Esto produce library(stringr)))phics)como resultado.

Esto produce ,%como resultado.

Esta respuesta (con el -iindicador eliminado) produce una salida idéntica a la entrada.

fbt
fuente
¿Los delimitadores deben ser espacios en coma, o solo es aceptable la coma?
steeldriver
Cualquiera de los dos está bien, pero necesito un carácter de comillas alrededor de la cadena, ya sea 'o ".
fbt
¿Soy el primero en notar que los datos de entrada y el script para procesarlos son completamente incompatibles? No habrá salida.
ctrl-alt-delor
El script que enumeré es cómo genero los datos de entrada. Alguien lo pidió. Los datos de entrada reales podrían ser algo como esto . Tenga en cuenta que Github cambia el formato para eliminar las nuevas líneas.
fbt

Respuestas:

19

Puede agregar comillas con sed y luego combinar líneas con pegar , así:

sed 's/^\|$/"/g'|paste -sd, -

Si está ejecutando un sistema basado en GNU coreutils (es decir, Linux), puede omitir el seguimiento '-'.

Si ingresa datos que tienen terminaciones de línea de estilo DOS (como sugirió @phk), puede modificar el comando de la siguiente manera:

sed 's/\r//;s/^\|$/"/g'|paste -sd, -
zepelín
fuente
1
En MacOS (y tal vez otros), deberá incluir un guión para indicar que la entrada es de stdin en lugar de un archivo:sed 's/^\|$/"/g'|paste -sd, -
cherdt
Es cierto que la versión de "coreutils" de pegar aceptará ambas formas, pero "-" es más POSIX. Gracias !
zeppelin
2
O solo con sed:sed 's/.*/"&"/;:l;N;s/\n\(.*\)$/, "\1"/;tl'
Trauma digital
1
@fbt La nota que agregué al final de mi respuesta también se aplica aquí.
phk
1
@DigitalTrauma: no es realmente una buena idea; eso sería muy lento (incluso podría colgarse con archivos enormes): vea las respuestas al QI vinculado en mi comentario sobre el Q aquí; lo bueno es usarlo pastesolo;)
don_crissti
8
Utilizando awk:
awk 'BEGIN { ORS="" } { print p"'"'"'"$0"'"'"'"; p=", " } END { print "\n" }' /path/to/list
Alternativa con menos escape de shell y, por lo tanto, más legible:
awk 'BEGIN { ORS="" } { print p"\047"$0"\047"; p=", " } END { print "\n" }' /path/to/list
Salida:
'd3heatmap', 'data.table', 'ggplot2', 'htmltools', 'htmlwidgets', 'metricsgraphics', 'networkD3', 'plotly', 'reshape2', 'scales', 'stringr'
Explicación:

La awksecuencia de comandos en sí sin todo el escape es BEGIN { ORS="" } { print p"'"$0"'"; p=", " } END { print "\n" }. Después de imprimir la primera entrada, pse establece la variable (antes de eso es como una cadena vacía). Con esta variable, pcada entrada (o en awk-speak: record ) tiene el prefijo y además se imprime con comillas simples a su alrededor. La awkvariable del separador de registro de salida ORSno es necesaria (ya que el prefijo lo hace por usted), por lo que está configurada para estar vacía en el BEGINing. Ah, y podríamos nuestro archivo ENDcon una nueva línea (por ejemplo, funciona con otras herramientas de procesamiento de texto); en caso de que no sea necesaria la parte con ENDy todo lo que está después (dentro de las comillas simples) se puede eliminar.

Nota

Si tiene finales de línea de estilo Windows / DOS ( \r\n), \nprimero debe convertirlos al estilo UNIX ( ). Para hacer esto, puede poner tr -d '\015'al principio de su tubería:

tr -d '\015' < /path/to/input.list | awk […] > /path/to/output

(Suponiendo que no tiene ningún uso para \rs en su archivo. Suposición muy segura aquí).

Alternativamente, simplemente ejecute dos2unix /path/to/input.listuna vez para convertir el archivo en el lugar.

phk
fuente
Cuando ejecuto este comando, me sale ', 'stringr23aphicscomo la salida.
fbt
@fbt Vea mi última nota.
phk
2
print p"'"'"'"$0"'"'"'"; p=", "—¡Santas citas, Batman!
wchargin
Lo sé, cierto‽ :) Pensé en mencionar que en muchos shells la impresión p"'\''"$0"'\''";también habría funcionado (aunque no es POSIXy), o alternativamente usando bashlas cadenas de comillas C ( $'') incluso solo print p"\'"$0"\'";( aunque podría haber requerido duplicar otras barras invertidas), pero hay ya el otro método que usa awkel carácter se escapa.
phk
Wow, no puedo creer que te hayas dado cuenta. Gracias.
fbt
6

Como muestra la respuesta vinculada de @ don_crissti , la opción de pegar limita con increíblemente rápido: la tubería del kernel de Linux es más eficiente de lo que hubiera creído si no lo hubiera probado. Sorprendentemente, si puede estar satisfecho con una coma que separa los elementos de su lista en lugar de una coma + espacio, una tubería de pegado

(paste -d\' /dev/null - /dev/null | paste -sd, -) <input

es más rápido que incluso un flexprograma razonable (!)

%option 8bit main fast
%%
.*  { printf("'%s'",yytext); }
\n/(.|\n) { printf(", "); }

Pero si solo un rendimiento decente es aceptable (y si no está ejecutando una prueba de esfuerzo, no podrá medir las diferencias de factores constantes, todas son instantáneas) y desea flexibilidad con sus separadores y una razonable -liner-y-ness,

sed "s/.*/'&'/;H;1h;"'$!d;x;s/\n/, /g'

es tu boleto Sí, parece ruido de línea, pero el H;1h;$!d;xidioma es la forma correcta de sorber todo, una vez que puedes reconocer que todo se vuelve realmente fácil de leer, ess/.*/'&'/ seguido por un sorbo y un s/\n/, /g.


editar: bordeando lo absurdo, es bastante fácil obtener flexibilidad para vencer a todo lo demás hueco, solo dígale a stdio que no necesita la sincronización integrada multiproceso / controlador de señal:

%option 8bit main fast
%%
.+  { putchar_unlocked('\'');
      fwrite_unlocked(yytext,yyleng,1,stdout);
      putchar_unlocked('\''); }
\n/(.|\n) { fwrite_unlocked(", ",2,1,stdout); }

y bajo estrés, es 2-3 veces más rápido que las tuberías de pasta, que son al menos 5 veces más rápidas que todo lo demás.

jthill
fuente
1
(paste -d\ \'\' /dev/null /dev/null - /dev/null | paste -sd, -) <infile | cut -c2-haría coma + espacio @ más o menos la misma velocidad, aunque como notó, no es realmente flexible si necesita una cadena elegante como separador
don_crissti
Esas flexcosas son bastante buenas, hombre ... esta es la primera vez que veo a alguien publicar flexcódigo en este sitio ... ¡gran voto! Por favor, publique más de estas cosas.
don_crissti
@don_crissti ¡Gracias! Buscaré buenas oportunidades, sed / awk / whatnot son generalmente mejores opciones solo por el valor de conveniencia, pero a menudo también hay una respuesta flexible bastante fácil.
jthill
4

Perl

Python one-liner:

$ python -c "import sys; print ','.join([repr(l.strip()) for l in sys.stdin])" < input.txt                               
'd3heatmap','data.table','ggplot2','htmltools','htmlwidgets','metricsgraphics','networkD3','plotly','reshape2','scales','stringr'

Funciona de manera simple: redirigimos input.txt a stdin usando el <operador de shell , leemos cada línea en una lista .strip()eliminando nuevas líneas y repr()creando una representación entre comillas de cada línea. La lista se une en una gran cadena a través de la .join()función, con un ,separador

Alternativamente, podríamos usar +para concatenar comillas a cada línea despojada.

 python -c "import sys;sq='\'';print ','.join([sq+l.strip()+sq for l in sys.stdin])" < input.txt

Perl

Esencialmente, es la misma idea que antes: lea todas las líneas, elimine la nueva línea final, encierre entre comillas simples, coloque todo en array @cvs e imprima los valores de matriz unidos con comas.

$ perl -ne 'chomp; $sq = "\047" ; push @cvs,"$sq$_$sq";END{ print join(",",@cvs)   }'  input.txt                        

'd3heatmap', 'data.table', 'ggplot2', 'htmltools', 'htmlwidgets', 'metricsgraphics', 'networkD3', 'plotly', 'reshape2', 'scale', 'stringr'

Sergiy Kolodyazhnyy
fuente
IIRC, las pitones joindeberían ser capaces de tomar un iterador, por lo tanto, no debería haber necesidad de materializar el ciclo stdin en una lista
iruvar
@iruvar Sí, excepto que mire la salida deseada de OP: quieren que se cite cada palabra, y necesitamos eliminar las nuevas líneas finales para garantizar que la salida sea una línea. ¿Tienes una idea de cómo hacerlo sin una lista de comprensión?
Sergiy Kolodyazhnyy
3

Creo que lo siguiente debería funcionar bien, suponiendo que sus datos estén en el texto del archivo

d3heatmap
data.table
ggplot2
htmltools
htmlwidgets
metricsgraphics
networkD3
plotly
reshape2
scales
stringr

Usemos matrices que tienen la sustitución en frío:

#!/bin/bash
input=( $(cat text) ) 
output=( $(
for i in ${input[@]}
        do
        echo -ne "'$i',"
done
) )
output=${output:0:-1}
echo ${output//,/, }

El resultado del script debe ser el siguiente:

'd3heatmap', 'data.table', 'ggplot2', 'htmltools', 'htmlwidgets', 'metricsgraphics', 'networkD3', 'plotly', 'reshape2', 'scales', 'stringr'

Creo que esto era lo que estabas buscando?

Charles van der Genugten
fuente
1
Buena solución Pero si bien OP no lo solicitó explícitamente bashy si bien es seguro suponer que alguien podría usarlo (después de todo AFAIK es el shell más utilizado), aún no se debe dar por sentado. Además, hay partes que podría hacer para un mejor trabajo al citar (poner comillas dobles). Por ejemplo, aunque es improbable que los nombres de los paquetes tengan espacios, sigue siendo una buena convención citar variables en lugar de no, es posible que desee ejecutar shellcheck.net sobre él y ver las notas y explicaciones allí.
phk
2

A menudo tengo un escenario muy similar: copio una columna de Excel y quiero convertir el contenido en una lista separada por comas (para su uso posterior en una consulta SQL como ... WHERE col_name IN <comma-separated-list-here>).

Esto es lo que tengo en mi .bashrc:

function lbl {
    TMPFILE=$(mktemp)
    cat $1 > $TMPFILE
    dos2unix $TMPFILE
    (echo "("; cat $TMPFILE; echo ")") | tr '\n' ',' | sed -e 's/(,/(/' -e 's/,)/)/' -e 's/),/)/'
    rm $TMPFILE
}

Luego ejecuto lbl("línea por línea") en la línea cmd que espera la entrada, pego el contenido del portapapeles, presiono <C-D>y la función devuelve la entrada rodeada (). Esto se ve así:

$ lbl
1
2
3
dos2unix: converting file /tmp/tmp.OGM6UahLTE to Unix format ...
(1,2,3)

(No recuerdo por qué puse el dos2unix aquí, presumiblemente porque esto a menudo causa problemas en la configuración de mi empresa).

Rolf
fuente
1

Algunas versiones de sed actúan un poco diferente, pero en mi Mac, puedo manejar todo menos el "uniq" en sed:

sed -n -e '
# Skip commented library lines
/#/b
# Handle library lines
/library(/{
    # Replace line with just quoted filename and comma
    # Extra quoting is due to command-line use of a quote
    s/library(\([^)]*\))/'\''\1'\'', /
    # Exchange with hold, append new entry, remove the new-line
    x; G; s/\n//
    ${
        # If last line, remove trailing comma, print, quit
        s/, $//; p; b
    }
    # Save into hold
    x
}
${
    # Last line not library
    # Exchange with hold, remove trailing comma, print
    x; s/, $//; p
}
'

Desafortunadamente para arreglar la parte única, tienes que hacer algo como:

grep library Presentation.md | sort -u | sed -n -e '...'

--Pablo

PaulC
fuente
2
¡Bienvenido a Unix.stackexchange! te recomiendo el recorrido .
Stephen Rauch
0

Es curioso que para usar una lista de texto simple de paquetes R para instalarlos en R, nadie haya propuesto una solución usando esa lista directamente en R, pero pelee con bash, perl, python, awk, sed o lo que sea para poner comillas y comillas en el lista. Esto no es necesario en absoluto y además no resuelve cómo ingresar y usar la lista transformada en R.

Simplemente puede cargar el archivo de texto sin formato (dicho, packages.txt) como un marco de datos con una sola variable, que puede extraer como un vector, directamente utilizable por install.packages. Entonces, conviértalo en un objeto R utilizable e instale esa lista es solo:

df <- read.delim("packages.txt", header=F, strip.white=T, stringsAsFactors=F)
install.packages(df$V1)

O sin un archivo externo:

packages <-" 
d3heatmap
data.table
ggplot2
htmltools
htmlwidgets
metricsgraphics
networkD3
plotly
reshape2
scales
stringr
"
df <- read.delim(textConnection(packages), 
header=F, strip.white=T, stringsAsFactors=F)
install.packages(df$V1)
Fran
fuente