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.


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, -

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.


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.


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”


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ś?


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).


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ł


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)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.