Verwandeln Sie einzelne Zeilen in eine durch Kommas getrennte Liste mit Anführungszeichen

15

Ich habe die folgenden Daten (eine Liste von R-Paketen, die aus einer Rmarkdown-Datei analysiert wurden), die ich in eine Liste umwandeln möchte, die ich zur Installation an R übergeben kann:

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

Ich möchte die Liste in eine Liste des Formulars verwandeln:

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

Ich habe derzeit eine Bash-Pipeline, die von der Rohdatei zur obigen Liste führt:

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

Ich möchte einen Schritt hinzufügen, um die neuen Zeilen in die durch Kommas getrennte Liste umzuwandeln. Ich habe versucht hinzuzufügen tr '\n' '","', was fehlschlägt. Ich habe auch eine Reihe der folgenden Stack Overflow-Antworten ausprobiert, die ebenfalls fehlschlagen:

Dies führt library(stringr)))phics)zu dem Ergebnis.

Dies führt ,%zu dem Ergebnis.

Diese Antwort (mit -ientferntem Flag) erzeugt eine Ausgabe, die mit der Eingabe identisch ist.

fbt
quelle
Müssen die Begrenzer Komma-Leerzeichen sein oder ist Komma alleine akzeptabel?
Steeldriver
Entweder ist in Ordnung, aber ich brauche ein Anführungszeichen um die Zeichenfolge, entweder 'oder ".
fbt
Bin ich der Erste, der merkt, dass die Eingabedaten und das Skript, um sie zu verarbeiten, völlig inkompatibel sind. Es erfolgt keine Ausgabe.
Strg-Alt-Delor
Das Skript, das ich aufgelistet habe, ist, wie ich die Eingabedaten generiere. Jemand hat danach gefragt. Die tatsächlichen Eingangsdaten würde ungefähr so aussehen dies . Beachten Sie, dass Github die Formatierung ändert, um die neuen Zeilen zu entfernen.
fbt

Antworten:

19

Sie können mit sed Anführungszeichen hinzufügen und dann wie folgt Linien mit paste zusammenführen :

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

Wenn Sie ein GNU-Coreutils-basiertes System (z. B. Linux) ausführen, können Sie das Trailing weglassen '-'.

Wenn Sie Daten mit DOS-Zeilenenden eingeben (wie von @phk vorgeschlagen), können Sie den Befehl wie folgt ändern:

sed 's/\r//;s/^\|$/"/g'|paste -sd, -
Zeppelin
quelle
1
Unter MacOS (und möglicherweise auch anderen) müssen Sie einen Bindestrich sed 's/^\|$/"/g'|paste -sd, -
einfügen,
Richtig, "coreutils" -Version von paste akzeptiert beide Formen, "-" ist jedoch mehr POSIX. Danke !
Zeppelin
2
Oder einfach nur sedalleine:sed 's/.*/"&"/;:l;N;s/\n\(.*\)$/, "\1"/;tl'
Digitales Trauma
1
@fbt Der Hinweis, den ich jetzt am Ende meiner Antwort hinzugefügt habe, gilt auch hier.
Phk
1
@DigitalTrauma - keine wirklich gute Idee; das wäre sehr langsam (könnte sogar bei großen Dateien hängen bleiben) - siehe die Antworten auf das QI, das in meinem Kommentar zum Q hier verlinkt ist; das coole ding ist pastealleine zu benutzen ;)
don_crissti
8
Verwenden von awk:
awk 'BEGIN { ORS="" } { print p"'"'"'"$0"'"'"'"; p=", " } END { print "\n" }' /path/to/list
Alternative mit weniger Shell-Flucht und daher besser lesbar:
awk 'BEGIN { ORS="" } { print p"\047"$0"\047"; p=", " } END { print "\n" }' /path/to/list
Ausgabe:
'd3heatmap', 'data.table', 'ggplot2', 'htmltools', 'htmlwidgets', 'metricsgraphics', 'networkD3', 'plotly', 'reshape2', 'scales', 'stringr'
Erläuterung:

Das awkSkript selbst ohne all das Entrinnen ist BEGIN { ORS="" } { print p"'"$0"'"; p=", " } END { print "\n" }. Nach dem Ausdruck des ersten Eintrags wird die Variable pgesetzt (davor ist es wie eine leere Zeichenkette). Mit dieser Variablen wird pjeder Eintrag (oder in awk-speak: record ) vorangestellt und zusätzlich mit einfachen Anführungszeichen versehen. Die awkAusgabesatztrennungsvariable ORSwird nicht benötigt (da das Präfix dies für Sie erledigt), daher ist sie zum Zeitpunkt der BEGINEingabe leer . Oh und wir könnten unsere Datei ENDmit einem Zeilenumbruch versehen (zB so funktioniert es mit weiteren Textverarbeitungswerkzeugen); Sollte dies nicht benötigt werden, kann das Teil mit ENDund alles danach (innerhalb der einfachen Anführungszeichen) entfernt werden.

Hinweis

Wenn Sie Zeilenenden im Windows / DOS-Stil ( \r\n) haben, müssen Sie diese zuerst in den UNIX-Stil ( \n) konvertieren . Dazu können Sie tr -d '\015'am Anfang Ihrer Pipeline Folgendes einfügen:

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

(Vorausgesetzt, Sie haben keine Verwendung für \rs in Ihrer Datei. Sehr sichere Annahme hier.)

Alternativ können Sie dos2unix /path/to/input.listdie Datei auch einfach einmal ausführen, um sie direkt zu konvertieren.

phk
quelle
Wenn ich diesen Befehl ausführe, erhalte ich ', 'stringr23aphicsals Ausgabe.
fbt
@fbt Siehe meine neueste Notiz.
Phk
2
print p"'"'"'"$0"'"'"'"; p=", "- Heilige Zitate, Batman!
Wchargin
Ich habe darüber nachgedacht, zu erwähnen, dass Print in vielen Shells p"'\''"$0"'\''";auch funktioniert hätte (es ist jedoch nicht POSIXy), oder alternativ auch nur bashC- Anführungszeichen ( $'') zu verwenden print p"\'"$0"\'";(möglicherweise hätte man andere Backslashes verdoppeln müssen), aber es gibt welche bereits die andere Methode mit awk's Zeichen entkommt.
Phk
Wow, ich kann nicht glauben, dass du das herausgefunden hast. Vielen Dank.
fbt
6

Wie die verknüpfte Antwort von @ don_crissti zeigt, ist die Einfügeoption unglaublich schnell - das Piping des Linux-Kernels ist effizienter, als ich gedacht hätte, wenn ich es nicht gerade versucht hätte. Bemerkenswerterweise, wenn Sie mit einem einzelnen Komma zufrieden sind, das Ihre Listenelemente trennt, und nicht mit einem Komma + Leerzeichen, einer Einfügepipeline

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

ist schneller als selbst ein vernünftiges flexProgramm (!)

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

Aber wenn nur eine anständige Leistung akzeptabel ist (und wenn Sie keinen Stresstest durchführen, können Sie keine konstanten Faktorunterschiede messen, sie sind alle augenblicklich) und Sie wollen sowohl Flexibilität mit Ihren als auch vernünftige Trennzeichen -Liner-y-Ness,

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

ist dein Ticket. Ja, es sieht aus wie Leitungsrauschen, aber die H;1h;$!d;xRedewendung ist der richtige Weg, um alles zu schlürfen. Sobald Sie erkennen, dass das Ganze tatsächlich leicht zu lesen ist, s/.*/'&'/folgt ein Schlürfen und ein Schlürfen s/\n/, /g.


edit: an der grenze zum absurden, es ist ziemlich einfach flex zu bekommen, um alles andere hohl zu schlagen, sag einfach stdio, dass du die eingebaute multithread / signalhandler sync nicht brauchst:

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

und unter stress ist das 2-3x schneller als die paste pipelines, die selbst mindestens 5x schneller sind als alles andere.

jdoch
quelle
1
(paste -d\ \'\' /dev/null /dev/null - /dev/null | paste -sd, -) <infile | cut -c2-würde Komma + Leerzeichen @ so ziemlich die gleiche Geschwindigkeit machen, obwohl, wie Sie bemerkt haben, es nicht wirklich flexibel ist, wenn Sie eine ausgefallene Zeichenfolge als Trennzeichen benötigen
don_crissti
Das flexZeug ist verdammt cool, Mann ... das ist das erste Mal, dass ich jemanden flexauf dieser Seite sehe, der eine Postleitzahl schreibt ... große positive Bewertung! Bitte poste mehr von diesem Zeug.
don_crissti
@don_crissti Danke! Ich suche nach guten Gelegenheiten, sed / awk / whatnot sind normalerweise bessere Optionen nur für den Vorteilswert, aber es gibt oft auch eine ziemlich einfache flexible Antwort.
25.
4

Perl

Python Einzeiler:

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

Funktioniert auf einfache Weise: Wir leiten die Datei input.txt mithilfe des Shell- <Operators nach stdin um , lesen jede Zeile in eine Liste, .strip()entfernen neue Zeilen und repr()erstellen eine in Anführungszeichen gesetzte Darstellung jeder Zeile. Die Liste wird dann über die .join()Funktion ,als Trennzeichen zu einem großen String zusammengefügt

Alternativ könnten wir verwenden, +um Anführungszeichen zu jeder entfernten Zeile zu verketten.

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

Perl

Im Wesentlichen die gleiche Idee wie zuvor: Alle Zeilen lesen, Zeilenumbrüche entfernen, in einfache Anführungszeichen setzen, alles in das Array @cvs schreiben und mit Kommas verbundene Array-Werte ausdrucken.

$ 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', 'scales', 'stringr'

Sergiy Kolodyazhnyy
quelle
IIRC, Pythons joinsollten in der Lage sein, einen Iterator zu nehmen, daher sollte es nicht erforderlich sein, die stdin-Schleife in eine Liste aufzunehmen
iruvar
@iruvar Ja, mit Ausnahme der gewünschten Ausgabe von OP. Sie möchten, dass jedes Wort in Anführungszeichen gesetzt wird, und wir müssen nachfolgende Zeilenumbrüche entfernen, um sicherzustellen, dass die Ausgabe eine Zeile ist. Sie haben eine Idee, wie Sie das ohne Listenverständnis machen können?
Sergiy Kolodyazhnyy
3

Ich denke, das Folgende sollte in Ordnung sein, vorausgesetzt, Ihre Daten befinden sich im Dateitext

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

Verwenden wir Arrays, bei denen die Substitution kalt ist:

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

Die Ausgabe des Skripts sollte wie folgt aussehen:

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

Ich glaube, das war es, wonach du gesucht hast?

Charles van der Genugten
quelle
1
Gute Lösung. Aber obwohl OP nicht explizit danach gefragt hat bashund man davon ausgehen kann, dass jemand es verwendet (schließlich ist es die am häufigsten verwendete AFAIK-Shell), sollte es dennoch nicht als selbstverständlich angesehen werden. Es gibt auch Teile, die Sie besser zitieren können (doppelte Anführungszeichen). Während die Paketnamen wahrscheinlich keine Leerzeichen enthalten, ist es eine gute Konvention, Variablen in Anführungszeichen zu setzen, anstatt dies nicht zu tun. Möglicherweise möchten Sie shellcheck.net darüber ausführen und die dortigen Hinweise und Erklärungen lesen .
Phk
2

Ich habe oft ein sehr ähnliches Szenario: Ich kopiere eine Spalte aus Excel und möchte den Inhalt in eine durch Kommas getrennte Liste konvertieren (zur späteren Verwendung in einer SQL-Abfrage wie ... WHERE col_name IN <comma-separated-list-here>).

Das habe ich in meiner .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
}

Ich lblführe dann ("zeilenweise") die cmd-Zeile aus, die auf die Eingabe wartet, füge den Inhalt aus der Zwischenablage ein, drücke <C-D>und die Funktion gibt die Eingabe zurück, die von umgeben ist (). Das sieht so aus:

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

(Ich kann mich nicht erinnern, warum ich dos2unix hierher gebracht habe, vermutlich, weil dies häufig Probleme bei der Einrichtung meines Unternehmens verursacht.)

Rolf
quelle
1

Einige Versionen von sed verhalten sich etwas anders, aber auf meinem Mac kann ich alles außer dem "Uniq" in sed verarbeiten:

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

Unglücklicherweise muss man etwas tun, um das einzigartige Teil zu reparieren:

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

--Paul

PaulC
quelle
2
Willkommen bei Unix.stackexchange! Ich empfehle Ihnen die Tour .
Stephen Rauch
0

Es ist komisch, dass niemand, der eine Klartextliste von R-Paketen verwendet, um sie in R zu installieren, eine Lösung vorschlägt, die diese Liste direkt in R verwendet, aber mit bash, perl, python, awk, sed oder was auch immer kämpft, um Anführungszeichen und Kommas in das zu setzen aufführen. Dies ist überhaupt nicht notwendig und löst außerdem nicht, wie die transformierte Liste in R eingegeben und verwendet wird.

Sie können die Klartextdatei (sagte, packages.txt) einfach als Datenframe mit einer einzelnen Variablen laden, die Sie als Vektor extrahieren können und die direkt von verwendet werden kanninstall.packages . Konvertieren Sie es in ein verwendbares R-Objekt und installieren Sie diese Liste wie folgt:

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

Oder ohne externe Datei:

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
quelle