Przekształcanie oddzielnych wierszy w listę rozdzielaną przecinkami z wpisami cytowanymi

15

Mam następujące dane (listę pakietów R przeanalizowanych z pliku Rmarkdown), które chcę przekształcić w listę, którą mogę przekazać do R, aby zainstalować:

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

Chcę przekształcić listę w listę formularza:

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

Obecnie mam potok bash, który przechodzi z surowego pliku na powyższą listę:

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

Chcę dodać krok, aby przekształcić nowe wiersze w listę rozdzielaną przecinkami. Próbowałem dodać tr '\n' '","', co się nie udaje. Próbowałem również kilka następujących odpowiedzi Przepełnienie stosu, które również nie powiodły się:

W library(stringr)))phics)rezultacie powstaje .

W ,%rezultacie powstaje .

Ta odpowiedź (z -iusuniętą flagą) tworzy dane wyjściowe identyczne z danymi wejściowymi.

fbt
źródło
Czy separatory muszą być przecinkami, czy może sam przecinek jest akceptowalny?
steeldriver
Albo jest w porządku, ale ja potrzebuję znak cudzysłowu otaczającą ciąg, albo 'albo ".
fbt
Czy jako pierwszy zauważyłem, że dane wejściowe i skrypt do ich przetwarzania są całkowicie niezgodne. Nie będzie wyjścia.
ctrl-alt-delor
Skrypt, który wymieniłem, to sposób generowania danych wejściowych. Ktoś o to poprosił. Rzeczywiste dane wejściowe będą wyglądać jak ten . Pamiętaj, że Github zmienia formatowanie, aby usunąć nowe linie.
fbt

Odpowiedzi:

19

Możesz dodawać cudzysłowy za pomocą sed, a następnie scalać wiersze za pomocą wklejania :

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

Jeśli korzystasz z systemu opartego na jądrach GNU (tj. Linux), możesz pominąć końcowe '-'.

Jeśli dane wejściowe mają zakończenia linii w stylu DOS (jak sugeruje @phk), możesz zmodyfikować polecenie w następujący sposób:

sed 's/\r//;s/^\|$/"/g'|paste -sd, -
zepelin
źródło
1
Na MacOS (i być może inne), trzeba będzie zawierać myślnik oznacza, że wejście jest z stdin zamiast pliku:sed 's/^\|$/"/g'|paste -sd, -
cherdt
To prawda, że ​​wersja „coreutils” pasty akceptuje obie formy, ale „-” oznacza więcej POSIX. Dzięki !
zeppelin
2
Lub tylko sedsam:sed 's/.*/"&"/;:l;N;s/\n\(.*\)$/, "\1"/;tl'
Digital Trauma
1
@fbt Uwaga, którą dodałem teraz na końcu mojej odpowiedzi, dotyczy również tutaj.
phk
1
@DigitalTrauma - niezbyt dobry pomysł; byłoby to bardzo powolne (może nawet zawiesić się z dużymi plikami) - zobacz odpowiedzi na QI połączone w moim komentarzu do Q tutaj; fajną rzeczą jest korzystanie z niej pasteosobno;)
don_crissti
8
Używanie awk:
awk 'BEGIN { ORS="" } { print p"'"'"'"$0"'"'"'"; p=", " } END { print "\n" }' /path/to/list
Alternatywa z mniejszą ilością ucieczek powłoki, a zatem bardziej czytelna:
awk 'BEGIN { ORS="" } { print p"\047"$0"\047"; p=", " } END { print "\n" }' /path/to/list
Wynik:
'd3heatmap', 'data.table', 'ggplot2', 'htmltools', 'htmlwidgets', 'metricsgraphics', 'networkD3', 'plotly', 'reshape2', 'scales', 'stringr'
Wyjaśnienie:

Sam awkskrypt bez ucieczki jest BEGIN { ORS="" } { print p"'"$0"'"; p=", " } END { print "\n" }. Po wydrukowaniu pierwszego wpisu zmienna pjest ustawiana (wcześniej jest jak pusty ciąg). Dzięki tej zmiennej pkażdy wpis (lub in- awkmów: rekord ) jest poprzedzany i dodatkowo drukowany z pojedynczymi cudzysłowami wokół niego. awkZmienna separator rekordów wyjściowych ORSnie jest potrzebne (od prefiks robi to za Ciebie), więc jest on ustawiony na pusty w BEGINING. Aha i możemy nasz plik ENDz nową linią (np. Dzięki czemu działa z dalszymi narzędziami do przetwarzania tekstu); jeśli nie będzie to potrzebne, część z ENDi wszystko po nim (wewnątrz pojedynczych cudzysłowów) można usunąć.

Uwaga

Jeśli masz zakończenia linii w stylu Windows / DOS ( \r\n), musisz je \nnajpierw przekonwertować na styl UNIX ( ). Aby to zrobić, możesz umieścić tr -d '\015'na początku swojego potoku:

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

(Zakładając, że \rw twoim pliku nie ma zastosowania dla s. Bardzo bezpieczne założenie tutaj.)

Alternatywnie, wystarczy dos2unix /path/to/input.listraz uruchomić, aby przekonwertować plik w miejscu.

phk
źródło
Kiedy uruchamiam to polecenie, otrzymuję ', 'stringr23aphicsjako wynik.
fbt
@fbt Zobacz moją ostatnią notatkę.
phk
2
print p"'"'"'"$0"'"'"'"; p=", "—Holy cytaty, Batman!
wchargin
Wiem, prawda‽ :) Pomyślałem o wspomnieniu, że w wielu powłokach wydruk p"'\''"$0"'\''";też by działał (choć nie jest POSIXY), lub alternatywnie użycie bashciągów cytowania C ( $'') nawet po prostu print p"\'"$0"\'";( choć może wymagać podwojenia innych odwrotnych ukośników), ale jest już inna metoda wykorzystująca awkznaki ucieczki.
phk
Wow, nie mogę uwierzyć, że to rozgryzłeś. Dziękuję Ci.
fbt
6

Jak pokazuje połączona odpowiedź @ don_crissti , opcja wklejania graniczy z niewiarygodnie szybkim - potokowanie jądra Linuksa jest wydajniejsze, niż bym się spodziewał, gdybym go nie wypróbował. Co ciekawe, jeśli możesz być zadowolony z pojedynczego przecinka oddzielającego elementy listy zamiast przecinka + spacji, wklej potok

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

jest szybszy niż nawet rozsądny flexprogram (!)

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

Ale jeśli tylko przyzwoita wydajność jest do zaakceptowania (a jeśli nie przeprowadzasz testu warunków skrajnych, nie będziesz w stanie zmierzyć różnic o stałym współczynniku, wszystkie są natychmiastowe) i chcesz zarówno elastyczności z separatorami, jak i rozsądnej -liner-y-ness,

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

jest twoim biletem. Tak, wygląda to na szum linii, ale H;1h;$!d;xidiom jest właściwym sposobem, aby wszystko zepsuć, gdy tylko zauważysz, że cała rzecz jest naprawdę łatwa do odczytania, po niej s/.*/'&'/następuje slurp i a s/\n/, /g.


edycja: granicząc z absurdem, dość łatwo jest wygrać, aby pokonać wszystko inne puste, po prostu powiedz stdio, że nie potrzebujesz wbudowanej synchronizacji wielowątkowości / obsługi sygnału:

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

i pod wpływem stresu jest 2-3 razy szybszy niż rurociągi wklejane, które same są co najmniej 5 razy szybsze niż wszystko inne.

jthill
źródło
1
(paste -d\ \'\' /dev/null /dev/null - /dev/null | paste -sd, -) <infile | cut -c2-zrobiłby przecinek + spację @ prawie tak samo szybko, jak zauważyłeś, nie jest to tak naprawdę elastyczne, jeśli potrzebujesz jakiegoś fantazyjnego ciągu jako separatora
don_crissti
Te flexrzeczy są cholernie fajne, stary ... po raz pierwszy widzę, jak ktoś publikuje flexkod na tej stronie ... duża opinia! Proszę zamieścić więcej takich rzeczy.
don_crissti
@don_crissti Thanks! Poszukam dobrych okazji, sed / awk / whatnot są zwykle lepszymi opcjami tylko dla wygody, ale często jest też dość łatwa odpowiedź elastyczna.
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'

Działa w prosty sposób - przekierowujemy input.txt na stdin za pomocą <operatora powłoki , odczytujemy każdy wiersz na liście, .strip()usuwając znaki nowego wiersza i repr()tworząc cytowaną reprezentację każdego wiersza. Lista jest następnie łączona w jeden duży ciąg za pomocą .join()funkcji, z ,separatorem

Alternatywnie możemy użyć +do połączenia cytatów z każdą linią pozbawioną linii.

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

Perl

Zasadniczo taki sam pomysł jak poprzednio: odczytaj wszystkie wiersze, usuń znak nowej linii, umieść w pojedynczych cudzysłowach, umieść wszystko w tablicy @cvs i wydrukuj wartości tablic połączone przecinkami.

$ 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
źródło
IIRC, pytony joinpowinny mieć możliwość wykonania iteratora, dlatego nie powinno być potrzeby materializowania pętli stdin do listy
iruvar
@iruvar Tak, z wyjątkiem spojrzenia na pożądany wynik OP - chcą, aby każde słowo było cytowane, i musimy usunąć końcowe znaki nowej linii, aby upewnić się, że wynik jest jednym wierszem. Masz pomysł, jak to zrobić bez zrozumienia listy?
Sergiy Kolodyazhnyy,
3

Myślę, że poniższe czynności powinny wystarczyć, zakładając, że dane znajdują się w tekście pliku

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

Użyjmy tablic, które mają podstawienie na zimno:

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

Dane wyjściowe skryptu powinny wyglądać następująco:

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

Wierzę, że tego właśnie szukałeś?

Charles van der Genugten
źródło
1
Niezłe rozwiązanie. Ale chociaż OP nie poprosił wprost o to bashi chociaż można bezpiecznie założyć, że ktoś może go użyć (w końcu AFAIK jest najczęściej używaną powłoką), nadal nie należy go uważać za pewnik. Są też części, w których możesz lepiej pracować przy cytowaniu (wstawianie podwójnych cudzysłowów). Na przykład, chociaż w nazwach pakietów raczej nie ma spacji, nadal dobrze jest cytować zmienne, a nie, możesz uruchomić na nim shellcheck.net i zobaczyć tam uwagi i objaśnienia.
phk
2

Często mam bardzo podobny scenariusz: kopiuję kolumnę z Excela i chcę przekonwertować zawartość na listę oddzieloną przecinkami (do późniejszego użycia w zapytaniu SQL, takim jak ... WHERE col_name IN <comma-separated-list-here>).

Oto, co mam w moim .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
}

Następnie uruchamiam lbl(„linia po linii”) na linii cmd, która czeka na dane wejściowe, wklejam zawartość ze schowka, naciśnij, <C-D>a funkcja zwraca dane otoczone (). Wygląda to tak:

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

(Nie pamiętam, dlaczego umieściłem tutaj dos2unix, prawdopodobnie dlatego, że często powoduje to problemy w konfiguracji mojej firmy).

Rolf
źródło
1

Niektóre wersje sed działają nieco inaczej, ale na moim Macu mogę obsłużyć wszystko oprócz „uniq” w 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
}
'

Niestety, aby naprawić unikalną część, musisz zrobić coś takiego:

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

--Paweł

PaulC
źródło
2
Witamy w Unix.stackexchange! Polecam wybrać się na wycieczkę .
Stephen Rauch
0

Zabawne, że aby użyć listy tekstowej R do zainstalowania ich w R, nikt nie zaproponował rozwiązania wykorzystującego tę listę bezpośrednio w R, ale walcz z bash, perl, python, awk, sed lub czymkolwiek innym, aby wstawić cudzysłowy i przecinki w lista. Nie jest to wcale konieczne, a ponadto nie rozwiązuje problemu wprowadzania i używania przekształconej listy w języku R.

Możesz po prostu załadować zwykły plik tekstowy (wspomniany packages.txt) jako ramkę danych z pojedynczą zmienną, którą możesz wyodrębnić jako wektor, bezpośrednio przez install.packages. Tak więc przekonwertuj go na przydatny obiekt R i zainstaluj tę listę:

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

Lub bez zewnętrznego pliku:

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
źródło