Transformer des lignes séparées en une liste séparée par des virgules avec des entrées entre guillemets

15

J'ai les données suivantes (une liste de packages R analysés à partir d'un fichier Rmarkdown), que je veux transformer en une liste que je peux transmettre à R pour installer:

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

Je veux transformer la liste en une liste du formulaire:

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

J'ai actuellement un pipeline bash qui va du fichier brut à la liste ci-dessus:

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

Je veux ajouter une étape pour transformer les nouvelles lignes en liste séparée par des virgules. J'ai essayé d'ajouter tr '\n' '","', ce qui échoue. J'ai également essayé un certain nombre de réponses Stack Overflow suivantes, qui échouent également:

Cela produit library(stringr)))phics)comme résultat.

Cela produit ,%comme résultat.

Cette réponse (avec l' -iindicateur supprimé), produit une sortie identique à l'entrée.

fbt
la source
Les délimiteurs doivent-ils être des virgules, ou la virgule seule est-elle acceptable?
Steeldriver
Soit c'est bien, mais j'ai besoin d'un guillemet entourant la chaîne, soit 'ou ".
fbt
Suis-je le premier à remarquer que les données d'entrée et le script pour les traiter sont totalement incompatibles. Il n'y aura pas de sortie.
ctrl-alt-delor
Le script que j'ai répertorié est la façon dont je génère les données d'entrée. Quelqu'un l'a demandé. Les données d'entrée réelles ressembleraient à ceci . Notez que Github modifie la mise en forme pour supprimer les nouvelles lignes.
fbt

Réponses:

19

Vous pouvez ajouter des guillemets avec sed puis fusionner les lignes avec paste , comme ça:

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

Si vous utilisez un système basé sur GNU coreutils (c'est-à-dire Linux), vous pouvez omettre la fin '-'.

Si vous entrez des données avec des fins de ligne de style DOS (comme suggéré par @phk), vous pouvez modifier la commande comme suit:

sed 's/\r//;s/^\|$/"/g'|paste -sd, -
Zeppelin
la source
1
Sur MacOS (et peut-être d'autres), vous devrez inclure un tiret pour indiquer que l'entrée provient de stdin plutôt que d'un fichier:sed 's/^\|$/"/g'|paste -sd, -
cherdt
Certes, la version "coreutils" de la pâte acceptera les deux formes, mais "-" est plus POSIX. THX !
zeppelin
2
Ou tout simplement avec sedseul:sed 's/.*/"&"/;:l;N;s/\n\(.*\)$/, "\1"/;tl'
Digital Trauma
1
@fbt La note que j'ai ajoutée à la fin de ma réponse s'applique également ici.
phk
1
@DigitalTrauma - pas vraiment une bonne idée; ce serait très lent (pourrait même se bloquer avec des fichiers énormes) - voir les réponses au QI lié dans mon commentaire sur le Q ici; la chose cool est d'utiliser pasteseul;)
don_crissti
8
En utilisant awk:
awk 'BEGIN { ORS="" } { print p"'"'"'"$0"'"'"'"; p=", " } END { print "\n" }' /path/to/list
Alternative avec moins d'échappements shell et donc plus lisible:
awk 'BEGIN { ORS="" } { print p"\047"$0"\047"; p=", " } END { print "\n" }' /path/to/list
Production:
'd3heatmap', 'data.table', 'ggplot2', 'htmltools', 'htmlwidgets', 'metricsgraphics', 'networkD3', 'plotly', 'reshape2', 'scales', 'stringr'
Explication:

Le awkscript lui-même sans tous les échappements est BEGIN { ORS="" } { print p"'"$0"'"; p=", " } END { print "\n" }. Après avoir imprimé la première entrée, la variable pest définie (avant cela, c'est comme une chaîne vide). Avec cette variable, pchaque entrée (ou en awk-speak: record ) est préfixée et imprimée en plus avec des guillemets simples autour d'elle. La awkvariable de séparation d'enregistrement de sortie ORSn'est pas nécessaire (puisque le préfixe le fait pour vous), elle est donc définie pour être vide au niveau de l' BEGINing. Oh et nous pourrions créer notre fichier ENDavec une nouvelle ligne (par exemple pour qu'il fonctionne avec d'autres outils de traitement de texte); si cela n'est pas nécessaire, la partie avec ENDet tout ce qui suit (à l'intérieur des guillemets simples) peut être supprimée.

Remarque

Si vous avez des fins de ligne de style Windows / DOS ( \r\n), vous devez d'abord les convertir en style UNIX ( \n). Pour ce faire, vous pouvez mettre tr -d '\015'au début de votre pipeline:

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

(En supposant que vous n'avez aucune utilité pour les \rs dans votre fichier. Hypothèse très sûre ici.)

Sinon, exécutez simplement dos2unix /path/to/input.listune fois pour convertir le fichier sur place.

phk
la source
Lorsque j'exécute cette commande, j'obtiens ', 'stringr23aphicscomme sortie.
fbt
@fbt Voir ma dernière note.
phk
2
print p"'"'"'"$0"'"'"'"; p=", "—Bonnes citations, Batman!
wchargin
Je sais, c'est vrai :) J'ai pensé à mentionner que dans de nombreux shells, l'impression p"'\''"$0"'\''";aurait également fonctionné (ce n'est pas POSIXy cependant), ou alternativement en utilisant bashles chaînes de guillemets C ( $'') même juste print p"\'"$0"\'";(cela aurait peut-être dû doubler d'autres antislashs) mais il y a déjà l'autre méthode utilisant awkles caractères d'échappement de.
phk
Wow, je ne peux pas croire que vous ayez compris cela. Je vous remercie.
fbt
6

Comme le montre la réponse liée de @ don_crissti , l'option de collage est incroyablement rapide - la tuyauterie du noyau linux est plus efficace que je ne l'aurais cru si je ne l'avais pas essayé maintenant. Remarquablement, si vous pouvez être satisfait d'une seule virgule séparant vos éléments de liste plutôt que d'une virgule + espace, un pipeline de collage

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

est plus rapide que même un flexprogramme raisonnable (!)

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

Mais si des performances décentes sont acceptables (et si vous n'effectuez pas de test de stress, vous ne pourrez pas mesurer les différences de facteur constant, elles sont toutes instantanées) et vous voulez à la fois de la flexibilité avec vos séparateurs et raisonnable -liner-y-ness,

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

est votre ticket. Oui, cela ressemble à du bruit de ligne, mais l' H;1h;$!d;xidiome est la bonne façon de tout aspirer, une fois que vous pouvez reconnaître que le tout est réellement facile à lire, il est s/.*/'&'/suivi d'un slurp et d'un s/\n/, /g.


edit: à la limite de l'absurde, il est assez facile d'obtenir du flex pour battre tout le reste creux, dites simplement à stdio que vous n'avez pas besoin de la synchronisation multithread / signalhandler intégrée:

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

et sous stress, c'est 2-3 fois plus rapide que les pipelines de pâte, qui sont eux-mêmes au moins 5 fois plus rapides que tout le reste.

jthill
la source
1
(paste -d\ \'\' /dev/null /dev/null - /dev/null | paste -sd, -) <infile | cut -c2-ferait virgule + espace @ à peu près à la même vitesse, mais comme vous l'avez noté, ce n'est pas vraiment flexible si vous avez besoin d'une chaîne de fantaisie comme séparateur
don_crissti
Ce flextruc est sacrément cool mec ... c'est la première fois que je vois quelqu'un poster du flexcode sur ce site ... gros vote positif! Veuillez poster plus de ces trucs.
don_crissti
@don_crissti Merci! Je vais chercher de bonnes opportunités, sed / awk / whatnot sont généralement de meilleures options juste pour la valeur de commodité mais il y a souvent une réponse flexible assez facile aussi.
2017
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'

Fonctionne de manière simple - nous redirigeons input.txt vers stdin en utilisant l' <opérateur du shell , lisons chaque ligne dans une liste en .strip()supprimant les nouvelles lignes et en repr()créant une représentation entre guillemets de chaque ligne. La liste est ensuite jointe en une seule grande chaîne via la .join()fonction, avec ,comme séparateur

Alternativement, nous pourrions utiliser +pour concaténer des guillemets à chaque ligne supprimée.

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

Perl

Essentiellement la même idée qu'auparavant: lire toutes les lignes, supprimer la nouvelle ligne, insérer des guillemets simples, tout mettre dans le tableau @cvs et imprimer les valeurs du tableau jointes par des virgules.

$ 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
la source
IIRC, les pythons joindevraient pouvoir prendre un itérateur donc il ne devrait pas être nécessaire de matérialiser la boucle stdin à une liste
iruvar
@iruvar Oui, sauf regardez la sortie souhaitée de OP - ils veulent que chaque mot soit cité, et nous devons supprimer les retours à la ligne pour garantir que la sortie est une ligne. Vous avez une idée comment faire cela sans comprendre la liste?
Sergiy Kolodyazhnyy
3

Je pense que ce qui suit devrait très bien faire, en supposant que vos données soient dans le texte du fichier

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

Utilisons des tableaux qui ont la substitution à froid:

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

La sortie du script doit être la suivante:

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

Je crois que c'était ce que tu cherchais?

Charles van der Genugten
la source
1
Belle solution. Mais bien que OP n'ait pas explicitement demandé bashet bien qu'il soit sûr de supposer que quelqu'un pourrait l'utiliser (après tout AFAIK, c'est le shell le plus utilisé), il ne devrait toujours pas être pris pour acquis. En outre, il y a des pièces que vous pourriez donc améliorer le travail de soumission (en mettant des guillemets doubles). Par exemple, bien qu'il soit peu probable que les noms de packages contiennent des espaces, il est toujours préférable de citer des variables plutôt que de ne pas le faire, vous pouvez exécuter shellcheck.net dessus et voir les notes et explications qui s'y trouvent .
phk
2

J'ai souvent un scénario très similaire: je copie une colonne d'Excel et je veux convertir le contenu en une liste séparée par des virgules (pour une utilisation ultérieure dans une requête SQL comme ... WHERE col_name IN <comma-separated-list-here>).

Voici ce que j'ai dans mon .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
}

Je lance ensuite lbl("ligne par ligne") sur la ligne cmd qui attend l'entrée, collez le contenu du presse-papiers, appuyez sur <C-D>et la fonction renvoie l'entrée entourée de (). Cela ressemble à ceci:

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

(Je ne me souviens pas pourquoi j'ai mis le dos2unix ici, probablement parce que cela cause souvent des problèmes dans la configuration de mon entreprise.)

Rolf
la source
1

Certaines versions de sed agissent un peu différemment, mais sur mon mac, je peux tout gérer sauf le "uniq" dans 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
}
'

Malheureusement, pour réparer la pièce unique, vous devez faire quelque chose comme:

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

--Paul

PaulC
la source
2
Bienvenue sur Unix.stackexchange! Je vous recommande de faire le tour .
Stephen Rauch
0

C'est drôle que pour utiliser une liste en clair de packages R pour les installer dans R, personne n'a proposé une solution utilisant cette liste directement dans R mais se battre avec bash, perl, python, awk, sed ou quoi que ce soit pour mettre des guillemets et des virgules dans le liste. Cela n'est pas du tout nécessaire et ne résout pas non plus la façon dont la saisie et l'utilisation de la liste transformée dans R.

Vous pouvez simplement charger le fichier texte brut (dit packages.txt) sous forme de trame de données avec une seule variable, que vous pouvez extraire comme vecteur, directement utilisable par install.packages. Donc, convertissez-le en un objet R utilisable et installez cette liste est juste:

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

Ou sans fichier externe:

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
la source