Mengubah baris yang terpisah menjadi daftar yang dipisahkan koma dengan entri yang dikutip

15

Saya memiliki data berikut (daftar paket R yang diuraikan dari file Rmarkdown), yang ingin saya ubah menjadi daftar yang dapat saya sampaikan kepada R untuk diinstal:

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

Saya ingin mengubah daftar menjadi daftar formulir:

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

Saat ini saya memiliki pipa bash yang beralih dari file mentah ke daftar di atas:

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

Saya ingin menambahkan langkah untuk mengubah baris baru ke daftar yang dipisahkan koma. Saya sudah mencoba menambahkan tr '\n' '","', yang gagal. Saya juga mencoba sejumlah jawaban Stack Overflow berikut, yang juga gagal:

Ini menghasilkan library(stringr)))phics)sebagai hasilnya.

Ini menghasilkan ,%sebagai hasilnya.

Jawaban ini (dengan menghilangkan -iflag), menghasilkan output yang identik dengan input.

fbt
sumber
Apakah pembatas perlu koma-ruang, atau apakah koma sendiri dapat diterima?
steeldriver
Baik baik-baik saja, tetapi saya perlu karakter kutipan di sekeliling string, baik 'atau ".
fbt
Apakah saya orang pertama yang memperhatikan bahwa input data dan skrip untuk memprosesnya, sepenuhnya tidak kompatibel. Tidak akan ada output.
ctrl-alt-delor
Script yang saya daftarkan adalah bagaimana saya menghasilkan data input. Seseorang memintanya. Data input aktual akan terlihat seperti ini . Perhatikan bahwa Github mengubah pemformatan untuk menghapus baris baru.
fbt

Jawaban:

19

Anda dapat menambahkan kutipan dengan sed dan kemudian menggabungkan garis dengan tempel , seperti itu:

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

Jika Anda menjalankan sistem berbasis GNU coreutils (yaitu Linux), Anda dapat menghilangkan trailing '-'.

Jika Anda memasukkan data yang memiliki akhiran garis gaya-DOS (seperti yang disarankan @phk), Anda dapat memodifikasi perintah sebagai berikut:

sed 's/\r//;s/^\|$/"/g'|paste -sd, -
zeppelin
sumber
1
Pada MacOS (dan mungkin yang lain), Anda harus menyertakan tanda hubung untuk menunjukkan bahwa inputnya berasal dari stdin daripada file:sed 's/^\|$/"/g'|paste -sd, -
cherdt
Benar, versi "coreutils" dari pasta akan menerima kedua bentuk, tetapi "-" lebih POSIX. Terima kasih !
zeppelin
2
Atau hanya dengan sedsendirian:sed 's/.*/"&"/;:l;N;s/\n\(.*\)$/, "\1"/;tl'
Trauma Digital
1
@ fbt Catatan yang sekarang saya tambahkan di akhir jawaban saya juga berlaku di sini.
phk
1
@DigitalTrauma - bukan ide yang bagus; itu akan sangat lambat (bahkan mungkin hang dengan file besar) - lihat jawaban untuk QI yang ditautkan dalam komentar saya pada Q di sini; yang keren adalah menggunakan pastesendiri;)
don_crissti
8
Menggunakan awk:
awk 'BEGIN { ORS="" } { print p"'"'"'"$0"'"'"'"; p=", " } END { print "\n" }' /path/to/list
Alternatif dengan lebih sedikit shell yang keluar dan karenanya lebih mudah dibaca:
awk 'BEGIN { ORS="" } { print p"\047"$0"\047"; p=", " } END { print "\n" }' /path/to/list
Keluaran:
'd3heatmap', 'data.table', 'ggplot2', 'htmltools', 'htmlwidgets', 'metricsgraphics', 'networkD3', 'plotly', 'reshape2', 'scales', 'stringr'
Penjelasan:

The awkScript itu sendiri tanpa semua melarikan diri adalah BEGIN { ORS="" } { print p"'"$0"'"; p=", " } END { print "\n" }. Setelah mencetak entri pertama variabel pditetapkan (sebelum itu seperti string kosong). Dengan variabel ini psetiap entri (atau dalam awk-speak: record ) diawali dan dicetak dengan tanda kutip tunggal di sekitarnya. The awkvariabel pemisah record keluaran ORStidak diperlukan (karena awalan melakukannya untuk Anda) sehingga diatur menjadi kosong di BEGINing. Oh dan kami mungkin file kami ENDdengan baris baru (mis. Sehingga bekerja dengan alat pemrosesan teks lebih lanjut); jika ini tidak diperlukan, bagian dengan ENDdan segala sesuatu setelahnya (di dalam tanda kutip tunggal) dapat dihapus.

Catatan

Jika Anda memiliki ujung garis gaya Windows / DOS ( \r\n), Anda harus mengubahnya menjadi gaya UNIX ( \n) terlebih dahulu. Untuk melakukan ini, Anda dapat meletakkan tr -d '\015'di awal pipa Anda:

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

(Dengan asumsi Anda tidak memiliki penggunaan untuk \rs dalam file Anda. Asumsi yang sangat aman di sini.)

Atau, jalankan dos2unix /path/to/input.listsekali saja untuk mengonversi file di tempat.

phk
sumber
Ketika saya menjalankan perintah ini, saya mendapatkan ', 'stringr23aphicssebagai output.
fbt
@ fbt Lihat catatan terbaru saya.
phk
2
print p"'"'"'"$0"'"'"'"; p=", "— Kutipan suci, Batman!
wchargin
Saya tahu, benar‽ :) Saya berpikir untuk menyebutkan bahwa dalam banyak cangkang, cetakan p"'\''"$0"'\''";juga akan bekerja (bukan POSIXy), atau sebagai alternatif menggunakan bashstring kutipan C ( $'') bahkan hanya print p"\'"$0"\'";(mungkin perlu menggandakan backslash lainnya), tetapi ada sudah metode lain menggunakan awkkarakter lolos.
phk
Wow, saya tidak percaya Anda tahu itu. Terima kasih.
fbt
6

Seperti jawaban @ don_crissti yang ditautkan menunjukkan, opsi tempel berbatasan dengan sangat cepat - perpipaan kernel linux lebih efisien daripada yang saya percaya jika saya tidak mencobanya sekarang. Hebatnya, jika Anda bisa bahagia dengan satu koma yang memisahkan item daftar Anda dan bukan koma + spasi, pipa tempel

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

lebih cepat daripada flexprogram yang masuk akal (!)

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

Tetapi jika kinerja yang layak dapat diterima (dan jika Anda tidak menjalankan stress test, Anda tidak akan dapat mengukur perbedaan faktor-konstan, semuanya instan) dan Anda menginginkan fleksibilitas dengan pemisah dan yang masuk akal -liner-y-ness,

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

adalah tiketmu. Ya, sepertinya garis kebisingan, tetapi H;1h;$!d;xidiom adalah cara yang tepat untuk menghirup semuanya, begitu Anda dapat mengenali bahwa semuanya menjadi mudah dibaca, s/.*/'&'/diikuti oleh slurp dan a s/\n/, /g.


sunting: berbatasan dengan absurd, itu cukup mudah untuk mendapatkan flex untuk mengalahkan segalanya kosong, cukup katakan stdio Anda tidak perlu builtin multithread / signalhandler sync:

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

dan di bawah tekanan itu 2-3x lebih cepat dari pipa pasta, yang dengan sendirinya setidaknya 5x lebih cepat dari yang lainnya.

jthill
sumber
1
(paste -d\ \'\' /dev/null /dev/null - /dev/null | paste -sd, -) <infile | cut -c2-akan melakukan koma + spasi @ kecepatan yang hampir sama meskipun seperti yang Anda catat, itu tidak benar-benar fleksibel jika Anda memerlukan beberapa string mewah sebagai pemisah
don_crissti
Hal flexitu sangat keren, pria ... ini adalah pertama kalinya saya melihat seseorang memposting flexkode di situs ini ... upvote besar! Silakan kirim lebih banyak hal ini.
don_crissti
@don_crissti Terima kasih! Saya akan mencari peluang bagus, sed / awk / yang lainnya biasanya merupakan pilihan yang lebih baik hanya untuk nilai kenyamanan tetapi seringkali juga ada jawaban fleksibel yang cukup mudah.
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'

Bekerja dengan cara sederhana - kami mengarahkan input.txt ke stdin menggunakan <operator shell , membaca setiap baris ke daftar dengan .strip()menghapus baris baru dan repr()membuat representasi kutipan dari setiap baris. Daftar ini kemudian bergabung menjadi satu string besar melalui .join()fungsi, dengan ,sebagai pemisah

Atau kita dapat menggunakan +untuk menggabungkan kutipan ke setiap baris yang dilucuti.

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

Perl

Ide dasarnya sama seperti sebelumnya: membaca semua baris, menghapus baris baru, menyertakan tanda kutip tunggal, memasukkan semuanya ke dalam array @cvs, dan mencetak nilai-nilai array yang digabungkan dengan koma.

$ 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
sumber
IIRC, ular sanca joinharus dapat mengambil iterator oleh karena itu seharusnya tidak perlu untuk mewujudkan loop stdin ke daftar
iruvar
@iruvar Ya, kecuali lihat output yang diinginkan OP - mereka ingin setiap kata dikutip, dan kita perlu menghapus trailing newlines untuk memastikan output adalah satu baris. Anda punya ide bagaimana melakukannya tanpa pemahaman daftar?
Sergiy Kolodyazhnyy
3

Saya pikir yang berikut ini akan baik-baik saja, dengan anggapan data Anda ada dalam teks file

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

Mari kita gunakan array yang substitusi turun dingin:

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

Output dari skrip harus sebagai berikut:

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

Saya percaya ini yang Anda cari?

Charles van der Genugten
sumber
1
Solusi yang bagus. Tapi sementara OP tidak secara eksplisit meminta bashdan sementara aman untuk berasumsi bahwa seseorang mungkin menggunakannya (setelah semua AFAIK itu adalah shell yang paling sering digunakan) masih belum bisa diterima begitu saja. Juga, ada bagian-bagian yang bisa membuat Anda lebih baik dalam mengutip (memasukkan tanda kutip ganda). Misalnya, walaupun nama paket tidak memiliki ruang di dalamnya, konvensi ini masih baik untuk mengutip variabel daripada tidak, Anda mungkin ingin menjalankan shellcheck.net di atasnya dan melihat catatan dan penjelasan di sana.
phk
2

Saya sering memiliki skenario yang sangat mirip: Saya menyalin kolom dari Excel dan ingin mengubah konten menjadi daftar yang dipisahkan koma (untuk penggunaan nanti dalam permintaan SQL seperti ... WHERE col_name IN <comma-separated-list-here> ).

Ini yang saya miliki di .bashrc saya:

function lbl {
    TMPFILE=$(mktemp)
    cat $1 > $TMPFILE
    dos2unix $TMPFILE
    (echo "("; cat $TMPFILE; echo ")") | tr '\n' ',' | sed -e 's/(,/(/' -e 's/,)/)/' -e 's/),/)/'
    rm $TMPFILE
}

Saya kemudian menjalankan lbl("baris demi baris") pada baris cmd yang menunggu input, menempelkan konten dari clipboard, tekan <C-D>dan fungsi mengembalikan input yang dikelilingi (). Ini terlihat seperti ini:

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

(Saya tidak ingat mengapa saya meletakkan dos2unix di sini, mungkin karena ini sering menyebabkan masalah dalam pengaturan perusahaan saya.)

Rolf
sumber
1

Beberapa versi sed bertindak sedikit berbeda, tetapi pada mac saya, saya dapat menangani semuanya kecuali "uniq" di 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
}
'

Sayangnya untuk memperbaiki bagian unik Anda harus melakukan sesuatu seperti:

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

--Paul

PaulC
sumber
2
Selamat datang di Unix.stackexchange! Saya sarankan Anda mengikuti tur .
Stephen Rauch
0

Sangat lucu bahwa menggunakan daftar teks biasa dari paket R untuk menginstalnya di R, tidak ada yang mengusulkan solusi menggunakan daftar itu langsung di R tetapi bertarung dengan bash, perl, python, awk, sed atau apa pun untuk menempatkan tanda kutip dan koma di daftar. Ini tidak perlu sama sekali dan terlebih lagi tidak menyelesaikan bagaimana input dan menggunakan daftar yang diubah dalam R.

Anda cukup memuat file teks biasa (kata, packages.txt) sebagai kerangka data dengan variabel tunggal, yang dapat Anda ekstrak sebagai vektor, langsung dapat digunakan oleh install.packages. Jadi, konversikan dalam objek R yang dapat digunakan dan instal daftar itu hanya:

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

Atau tanpa file eksternal:

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
sumber