Trasformazione di righe separate in un elenco separato da virgole con voci tra virgolette


15

Ho i seguenti dati (un elenco di pacchetti R analizzati da un file Rmarkdown), che voglio trasformare in un elenco che posso passare a R per installare:

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

Voglio trasformare l'elenco in un elenco del modulo:

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

Al momento ho una pipeline bash che va dal file raw all'elenco sopra:

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

Voglio aggiungere un passaggio per trasformare le nuove righe nell'elenco separato da virgole. Ho provato ad aggiungere tr '\n' '","', che non riesce. Ho anche provato alcune delle seguenti risposte Stack Overflow, che falliscono anche:

Questo produce library(stringr)))phics)come risultato.

Questo produce ,%come risultato.

Questa risposta (con il -iflag rimosso), produce un output identico all'input.


I delimitatori devono essere spazi-virgola o è accettabile solo la virgola?
Steeldriver,

O va bene, ma ho bisogno di un carattere di citazione che circonda la stringa, o 'o ".
FBT


Sono il primo a notare che i dati di input e lo script per elaborarli sono completamente incompatibili. Non ci sarà output.
ctrl-alt-delor

Lo script che ho elencato è come generare i dati di input. Qualcuno l'ha chiesto. I dati di input effettivi sarebbero simili a questi . Nota che Github modifica la formattazione per rimuovere le nuove righe.
FBT

Risposte:


19

Puoi aggiungere le virgolette con sed e quindi unire le linee con incolla , in questo modo:

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

Se stai eseguendo un sistema basato su coreutils GNU (cioè Linux), puoi omettere il trailing '-'.

Se si immettono dati con terminazioni di riga in stile DOS (come suggerito da @phk), è possibile modificare il comando come segue:

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

1
Su MacOS (e forse altri), dovrai includere un trattino per indicare che l'input proviene da stdin piuttosto che da un file:sed 's/^\|$/"/g'|paste -sd, -
cherdt

È vero, la versione "coreutils" di paste accetterà entrambi i moduli, ma "-" è più POSIX. Grazie !
Zeppelin,

2
O solo da sedsolo:sed 's/.*/"&"/;:l;N;s/\n\(.*\)$/, "\1"/;tl'
Digital Trauma

1
@fbt La nota che ho ora aggiunto alla fine della mia risposta si applica anche qui.
phk,

1
@DigitalTrauma - non è proprio una buona idea; sarebbe molto lento (potrebbe anche bloccarsi con file di grandi dimensioni) - vedere le risposte al QI collegato nel mio commento sul Q qui; il bello è usare pasteda solo;)
don_crissti

8
Utilizzando awk:
awk 'BEGIN { ORS="" } { print p"'"'"'"$0"'"'"'"; p=", " } END { print "\n" }' /path/to/list
Alternativa con meno escape della shell e quindi più leggibile:
awk 'BEGIN { ORS="" } { print p"\047"$0"\047"; p=", " } END { print "\n" }' /path/to/list
Produzione:
'd3heatmap', 'data.table', 'ggplot2', 'htmltools', 'htmlwidgets', 'metricsgraphics', 'networkD3', 'plotly', 'reshape2', 'scales', 'stringr'
Spiegazione:

Lo awkscript stesso senza tutte le fughe è BEGIN { ORS="" } { print p"'"$0"'"; p=", " } END { print "\n" }. Dopo aver stampato la prima voce, pviene impostata la variabile (prima che sia come una stringa vuota). Con questa variabile pogni voce (o in awk-speak: record ) è prefissata e inoltre stampata con virgolette singole attorno ad essa. La awkvariabile del separatore del record di output ORSnon è necessaria (poiché il prefisso lo sta facendo per te), quindi è impostata per essere vuota in BEGINing. Oh, e potremmo avere il nostro file ENDcon una nuova riga (ad esempio, quindi funziona con ulteriori strumenti di elaborazione del testo); se ciò non fosse necessario, la parte con ENDe tutto ciò che segue (all'interno delle virgolette singole) può essere rimossa.

Nota

Se hai terminazioni di linea in stile Windows / DOS ( \r\n), devi prima convertirle in stile UNIX ( \n). Per fare ciò puoi inserire tr -d '\015'all'inizio della tua pipeline:

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

(Supponendo che tu non abbia alcun uso per \rs nel tuo file. Presupposto molto sicuro qui.)

In alternativa, esegui semplicemente dos2unix /path/to/input.listuna volta per convertire il file sul posto.


Quando eseguo questo comando, ottengo ', 'stringr23aphicscome output.
FBT

@fbt Vedi la mia ultima nota.
phk,

2
print p"'"'"'"$0"'"'"'"; p=", "- citazioni allegre, Batman!
mercoledì

Lo so, giusto‽ :) Ho pensato di menzionare che in molte shell la stampa p"'\''"$0"'\''";avrebbe funzionato (non è POSIXy però), o in alternativa usare bashle stringhe di virgolette C ( $'') anche solo print p"\'"$0"\'";(potrebbe aver richiesto il raddoppio di altre barre rovesciate) ma c'è già l'altro metodo che usa awkil carattere di escape scappa.
phk,

Wow, non posso credere che tu l'abbia capito. Grazie.
ft

6

Come mostra la risposta collegata di @ don_crissti , l'opzione incolla rasenta incredibilmente veloce - il piping del kernel linux è più efficiente di quanto avrei creduto se non l'avessi provato solo ora. Sorprendentemente, se puoi essere soddisfatto di una singola virgola che separa gli elementi dell'elenco anziché una virgola + spazio, una pipeline incolla

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

è più veloce di un flexprogramma ragionevole (!)

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

Ma se sono accettabili solo prestazioni decenti (e se non stai eseguendo uno stress test, non sarai in grado di misurare eventuali differenze di fattore costante, sono tutte istantanee) e vuoi sia flessibilità con i tuoi separatori sia ragionevole -liner-y-ness,

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

è il tuo biglietto. Sì, sembra un rumore di linea, ma l' H;1h;$!d;xidioma è il modo giusto per assimilare tutto, una volta che puoi riconoscere che l'intera cosa diventa effettivamente facile da leggere, è s/.*/'&'/seguita da un borbottio e un s/\n/, /g.


modifica: al confine con l'assurdo, è abbastanza facile ottenere la flessibilità per battere tutto il resto vuoto, dì solo a stdio che non hai bisogno della sincronizzazione multithread / signalhandler integrata:

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

e sotto stress è 2-3 volte più veloce delle tubazioni in pasta, che sono esse stesse almeno 5 volte più veloci di tutto il resto.


1
(paste -d\ \'\' /dev/null /dev/null - /dev/null | paste -sd, -) <infile | cut -c2-farebbe virgola + spazio @ praticamente alla stessa velocità, come hai notato, non è molto flessibile se hai bisogno di una stringa di fantasia come separatore
don_crissti,

Quella flexroba è dannatamente bella amico ... questa è la prima volta che vedo qualcuno pubblicare flexcodice su questo sito ... grande voto! Si prega di pubblicare più di questo materiale.
don_crissti,

@don_crissti Grazie! Cercherò buone opportunità, sed / awk / whatnot di solito sono opzioni migliori solo per il valore di convenienza, ma spesso c'è anche una risposta flessibile piuttosto facile.
jillill

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'

Funziona in modo semplice: reindirizziamo input.txt in stdin usando l' <operatore della shell , leggiamo ogni riga in un elenco .strip()rimuovendo le nuove righe e repr()creando una rappresentazione quotata di ogni riga. L'elenco viene quindi unito in una grande stringa tramite la .join()funzione, con ,come separatore

In alternativa, potremmo usare +per concatenare le virgolette per ogni riga spogliata.

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

Perl

Sostanzialmente la stessa idea di prima: leggere tutte le righe, eliminare la riga finale, racchiudere tra virgolette singole, inserire tutto in array @cvs e stampare valori di array uniti con virgole.

$ 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'


IIRC, i pitoni joindovrebbero essere in grado di prendere un iteratore quindi non dovrebbe essere necessario materializzare il loop stdin in un elenco
iruvar,

@iruvar Sì, tranne guardare l'output desiderato di OP: vogliono che ogni parola venga citata e dobbiamo rimuovere le nuove righe finali per garantire che l'output sia una riga. Hai idea di come farlo senza una comprensione dell'elenco?
Sergiy Kolodyazhnyy,

3

Penso che quanto segue dovrebbe andare bene, supponendo che i tuoi dati siano nel testo del file

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

Usiamo le matrici che hanno la sostituzione a freddo:

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

L'output dello script dovrebbe essere il seguente:

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

Credo che questo fosse quello che stavi cercando?


1
Bella soluzione. Ma mentre OP non ha esplicitamente chiesto bashe mentre è sicuro supporre che qualcuno potrebbe usarlo (dopo tutto AFAIK è la shell più utilizzata), non dovrebbe ancora essere dato per scontato. Inoltre, ci sono parti per le quali potresti fare un lavoro migliore tra virgolette (inserendo virgolette doppie). Ad esempio, anche se è improbabile che i nomi dei pacchetti contengano spazi, è comunque una buona convenzione citare le variabili piuttosto che no, è possibile eseguire shellcheck.net su di esso e vedere le note e le spiegazioni lì.
phk,

2

Ho spesso uno scenario molto simile: copio una colonna da Excel e voglio convertire il contenuto in un elenco separato da virgole (per un uso successivo in una query SQL come ... WHERE col_name IN <comma-separated-list-here>).

Questo è quello che ho nel mio .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
}

Quindi corro lbl("riga per riga") sulla riga cmd che attende input, incollo il contenuto dagli appunti, premo <C-D>e la funzione restituisce l'input circondato da (). Sembra così:

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

(Non ricordo perché ho inserito Dos2unix qui, presumibilmente perché questo spesso causa problemi nella configurazione della mia azienda.)


1

Alcune versioni di sed agiscono in modo leggermente diverso, ma sul mio Mac posso gestire tutto tranne "uniq" in 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
}
'

Sfortunatamente per correggere la parte unica devi fare qualcosa del tipo:

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

--Paolo


2
Benvenuto in Unix.stackexchange! Ti consiglio di fare il tour .
Stephen Rauch,

0

È divertente che per usare un semplice elenco di pacchetti R per installarli in R, nessuno abbia proposto una soluzione usando quell'elenco direttamente in R ma combatti con bash, perl, python, awk, sed o qualunque cosa per inserire virgolette e virgole nel elenco. Ciò non è affatto necessario e inoltre non risolve il modo in cui immettere e utilizzare l'elenco trasformato in R.

Puoi semplicemente caricare il file di testo semplice (detto, packages.txt) come un frame di dati con una singola variabile, che puoi estrarre come vettore, direttamente utilizzabile da install.packages. Quindi, convertilo in un oggetto R utilizzabile e installa quell'elenco è solo:

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

O senza un file esterno:

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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.