Biến các dòng riêng biệt thành một danh sách được phân tách bằng dấu phẩy với các mục được trích dẫn


15

Tôi có dữ liệu sau (danh sách các gói R được phân tích cú pháp từ tệp Rmarkdown), mà tôi muốn chuyển thành danh sách tôi có thể chuyển đến R để cài đặt:

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

Tôi muốn biến danh sách thành một danh sách của mẫu:

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

Tôi hiện có một đường dẫn bash đi từ tệp thô vào danh sách trên:

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

Tôi muốn thêm một bước để biến các dòng mới vào danh sách được phân tách bằng dấu phẩy. Tôi đã thử thêm tr '\n' '","', nhưng không thành công. Tôi cũng đã thử một số câu trả lời Stack Overflow sau đây, cũng không thành công:

Điều này tạo ra library(stringr)))phics)như là kết quả.

Điều này tạo ra ,%như là kết quả.

Câu trả lời này (với -icờ bị loại bỏ), tạo ra đầu ra giống hệt với đầu vào.


Các dấu phân cách cần phải là dấu phẩy, hoặc dấu phẩy được chấp nhận?
Steeldo

Hoặc là tốt, nhưng tôi cần một ký tự trích dẫn xung quanh chuỗi, 'hoặc ".
fbt


Tôi có phải là người đầu tiên nhận thấy rằng dữ liệu đầu vào và tập lệnh xử lý nó, hoàn toàn không tương thích. Sẽ không có đầu ra.
ctrl-alt-delor

Kịch bản tôi liệt kê là cách tôi tạo dữ liệu đầu vào. Có người hỏi nó. Dữ liệu đầu vào thực tế sẽ trông giống như thế này . Lưu ý rằng Github thay đổi định dạng để xóa các dòng mới.
fbt

Câu trả lời:


19

Bạn có thể thêm dấu ngoặc kép với sed và sau đó hợp nhất các dòng với dán , như thế:

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

Nếu bạn đang chạy một hệ thống dựa trên lõi GNU (ví dụ Linux), bạn có thể bỏ qua dấu vết '-'.

Nếu bạn nhập dữ liệu có kết thúc dòng kiểu DOS (như @phk đề xuất), bạn có thể sửa đổi lệnh như sau:

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

1
Trên MacOS (và có thể cả những người khác), bạn sẽ cần bao gồm một dấu gạch ngang để chỉ ra rằng đầu vào là từ stdin chứ không phải là một tệp:sed 's/^\|$/"/g'|paste -sd, -
cherdt

Đúng, phiên bản "coreutils" của dán sẽ chấp nhận cả hai hình thức, nhưng "-" là POSIX hơn. Cám ơn !
zeppelin

2
Hoặc chỉ với sedmột mình:sed 's/.*/"&"/;:l;N;s/\n\(.*\)$/, "\1"/;tl'
Chấn thương kỹ thuật số

1
@fbt Ghi chú tôi thêm vào cuối câu trả lời của tôi cũng áp dụng ở đây.
phk

1
@DigitalTrauma - không thực sự là một ý tưởng tốt; điều đó sẽ rất chậm (thậm chí có thể bị treo với các tệp lớn) - xem câu trả lời cho QI được liên kết trong nhận xét của tôi về Q ở đây; điều tuyệt vời là sử dụng pastemột mình;)
don_crissti

8
Sử dụng awk:
awk 'BEGIN { ORS="" } { print p"'"'"'"$0"'"'"'"; p=", " } END { print "\n" }' /path/to/list
Thay thế với thoát vỏ ít hơn và do đó dễ đọc hơn:
awk 'BEGIN { ORS="" } { print p"\047"$0"\047"; p=", " } END { print "\n" }' /path/to/list
Đầu ra:
'd3heatmap', 'data.table', 'ggplot2', 'htmltools', 'htmlwidgets', 'metricsgraphics', 'networkD3', 'plotly', 'reshape2', 'scales', 'stringr'
Giải trình:

Các awkkịch bản mà không có tất cả các thoát là BEGIN { ORS="" } { print p"'"$0"'"; p=", " } END { print "\n" }. Sau khi in mục đầu tiên, biến pđược đặt (trước đó giống như một chuỗi rỗng). Với biến này, pmọi mục nhập (hoặc in awk-speak: record ) đều được thêm tiền tố và được in thêm với các dấu ngoặc đơn xung quanh nó. Các awkbiến tách kỷ lục sản lượng ORSlà không cần thiết (kể từ khi tiền tố đang làm điều đó cho bạn) vì vậy nó được thiết lập để được sản phẩm nào tại BEGINing. Ồ và chúng tôi có thể gửi tệp của mình ENDvới một dòng mới (ví dụ: để nó hoạt động với các công cụ xử lý văn bản khác); Nếu phần này không cần thiết ENDvà mọi thứ sau nó (bên trong dấu ngoặc đơn) có thể được gỡ bỏ.

Ghi chú

Nếu bạn có kết thúc dòng kiểu Windows / DOS ( \r\n), trước tiên bạn phải chuyển đổi chúng thành kiểu UNIX ( \n). Để làm điều này, bạn có thể đặt tr -d '\015'ở đầu đường ống của bạn:

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

(Giả sử bạn không sử dụng \rs trong tệp của mình. Giả định rất an toàn ở đây.)

Ngoài ra, chỉ cần chạy dos2unix /path/to/input.listmột lần để chuyển đổi tập tin tại chỗ.


Khi tôi chạy lệnh này, tôi nhận được ', 'stringr23aphicsnhư là đầu ra.
fbt

@fbt Xem ghi chú mới nhất của tôi.
phk

2
print p"'"'"'"$0"'"'"'"; p=", "Báo giá của người sói, Người dơi!
wchargein

Tôi biết, right‽ :) Tôi nghĩ về đề cập rằng trong nhiều vỏ In p"'\''"$0"'\''";sẽ cũng đã làm việc (nó không phải POSIXy dù), hoặc cách khác sử dụng bashcủa chuỗi trích dẫn C ( $'') thậm chí chỉ cần print p"\'"$0"\'";(có thể cần tăng gấp đôi backslashes khác mặc dù) nhưng có đã là phương thức khác sử dụng awkký tự thoát.
phk

Wow, tôi không thể tin rằng bạn đã tìm ra điều đó. Cảm ơn bạn.
fbt

6

Như câu trả lời được liên kết của @ don_crissti's cho thấy, tùy chọn dán có tốc độ cực nhanh - đường ống của hạt nhân linux hiệu quả hơn tôi có thể tin nếu tôi chưa thử nó. Đáng chú ý, nếu bạn có thể hài lòng với một dấu phẩy phân tách các mục danh sách của bạn thay vì dấu phẩy + dấu cách, một đường dẫn dán

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

còn nhanh hơn cả một flexchương trình hợp lý (!)

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

Nhưng nếu hiệu suất tốt chỉ có thể chấp nhận được (và nếu bạn không chạy thử nghiệm căng thẳng, bạn sẽ không thể đo bất kỳ sự khác biệt nào về yếu tố không đổi, tất cả đều tức thời) và bạn muốn cả hai linh hoạt với dải phân cách của mình và hợp lý -chủ-yess,

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

là vé của bạn Vâng, nó trông giống như tiếng ồn đường truyền, nhưng H;1h;$!d;xthành ngữ là cách phù hợp để làm lu mờ mọi thứ, một khi bạn có thể nhận ra rằng toàn bộ mọi thứ trở nên dễ đọc, nó được s/.*/'&'/theo sau bởi một tiếng lóng và a s/\n/, /g.


chỉnh sửa: giáp với sự ngớ ngẩn, khá dễ dàng để có được flex để đánh bại mọi thứ khác, chỉ cần nói với stdio bạn không cần đồng bộ hóa đa tín hiệu / tín hiệu dựng sẵn:

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

và chịu áp lực nhanh hơn 2-3 lần so với đường ống dán, bản thân chúng nhanh hơn ít nhất 5x so với mọi thứ khác.


1
(paste -d\ \'\' /dev/null /dev/null - /dev/null | paste -sd, -) <infile | cut -c2-sẽ thực hiện dấu phẩy + dấu cách @ khá nhiều tốc độ tương tự như bạn đã lưu ý, sẽ không thực sự linh hoạt nếu bạn cần một chuỗi ưa thích làm dấu phân cách
don_crissti

Thứ đó flexkhá là ngầu đấy ... đây là lần đầu tiên tôi thấy ai đó đăng flexmã trên trang này ... upvote lớn! Xin vui lòng gửi thêm các công cụ này.
don_crissti

@don_crissti Cảm ơn! Tôi sẽ tìm kiếm cơ hội tốt, sed / awk / whatnot thường là những lựa chọn tốt hơn chỉ vì giá trị tiện lợi nhưng thường cũng có một câu trả lời khá dễ dàng.
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'

Hoạt động theo cách đơn giản - chúng tôi chuyển hướng input.txt thành stdin bằng <toán tử shell , đọc từng dòng vào danh sách với .strip()việc xóa dòng mới và repr()tạo đại diện được trích dẫn của từng dòng. Danh sách này sau đó được nối vào một chuỗi lớn thông qua .join()chức năng, với ,tư cách là dấu phân cách

Ngoài ra, chúng tôi có thể sử dụng +để nối các trích dẫn cho mỗi dòng bị tước.

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

Perl

Về cơ bản, cùng một ý tưởng như trước: đọc tất cả các dòng, vạch dòng mới, đặt trong các dấu ngoặc đơn, nhét mọi thứ vào mảng @cvs và in ra các giá trị mảng được nối bằng dấu phẩy.

$ perl -ne 'chomp; $sq = "\047" ; push @cvs,"$sq$_$sq";END{ print join(",",@cvs)   }'  input.txt                        

'd3heatmap', 'data.table', 'ggplot2', 'htmltools', 'htmlwidgets', 'metricsgraphics', 'networkD3', 'mưu đồ', 'reshape2', 'scale', 'stringr'


IIRC, trăn joinsẽ có thể sử dụng một trình vòng lặp do đó không cần phải cụ thể hóa vòng lặp stdin thành một danh sách
iruvar

@iruvar Có, ngoại trừ nhìn vào đầu ra mong muốn của OP - họ muốn mỗi từ được trích dẫn và chúng tôi cần xóa các dòng mới để đảm bảo đầu ra là một dòng. Bạn có một ý tưởng làm thế nào để làm điều đó mà không có một danh sách hiểu?
Sergiy Kolodyazhnyy

3

Tôi nghĩ rằng những điều sau đây sẽ làm tốt, giả sử dữ liệu của bạn nằm trong văn bản tệp

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

Chúng ta hãy sử dụng các mảng có sự thay thế lạnh:

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

Đầu ra của tập lệnh nên như sau:

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

Tôi tin rằng đây là những gì bạn đang tìm kiếm?


1
Giải pháp tốt đẹp. Nhưng trong khi OP không yêu cầu rõ ràng bashvà trong khi vẫn an toàn khi cho rằng ai đó có thể sử dụng nó (sau tất cả AFAIK thì đó là vỏ được sử dụng nhiều nhất), nó vẫn không nên được coi là điều hiển nhiên. Ngoài ra, có những phần bạn có thể để một công việc tốt hơn trong việc trích dẫn (đặt dấu ngoặc kép). Ví dụ, trong khi các tên gói không có khoảng trắng trong đó, thì vẫn là một quy ước tốt để trích dẫn các biến thay vì không, bạn có thể muốn chạy shellcheck.net qua nó và xem các ghi chú và giải thích ở đó.
phk

2

Tôi thường có một kịch bản rất giống nhau: Tôi sao chép một cột từ Excel và muốn chuyển đổi nội dung thành một danh sách được phân tách bằng dấu phẩy (để sử dụng sau này trong truy vấn SQL như thế nào ... WHERE col_name IN <comma-separated-list-here>).

Đây là những gì tôi có trong .bashrc của mình:

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

Sau đó tôi chạy lbl("từng dòng một") trên dòng cmd chờ đầu vào, dán nội dung từ bảng ghi tạm, nhấn <C-D>và hàm trả về đầu vào được bao quanh (). Điều này trông giống như vậy:

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

(Tôi không nhớ tại sao tôi đặt dos2unix ở đây, có lẽ vì điều này thường gây rắc rối trong thiết lập của công ty tôi.)


1

Một số phiên bản của sed hoạt động hơi khác một chút, nhưng trên máy mac của tôi, tôi có thể xử lý mọi thứ trừ "uniq" trong 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
}
'

Thật không may để sửa phần duy nhất bạn phải làm một cái gì đó như:

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

--Paul


2
Chào mừng bạn đến với Unix.stackexchange! Tôi khuyên bạn nên đi tour .
Stephen Rauch

0

Thật buồn cười là khi sử dụng danh sách văn bản đơn giản của các gói R để cài đặt chúng trong R, không ai đề xuất giải pháp sử dụng danh sách đó trực tiếp trong R nhưng chiến đấu với bash, perl, python, awk, sed hoặc bất cứ điều gì để đặt dấu ngoặc kép và dấu phẩy vào danh sách. Điều này hoàn toàn không cần thiết và hơn nữa không giải quyết được cách nhập và sử dụng danh sách đã chuyển đổi trong R.

Bạn có thể chỉ cần tải tệp văn bản đơn giản (đã nói packages.txt) dưới dạng một khung dữ liệu với một biến duy nhất, mà bạn có thể trích xuất dưới dạng một vectơ, có thể sử dụng trực tiếp bằng cách install.packages. Vì vậy, chuyển đổi nó trong một đối tượng R có thể sử dụng và cài đặt danh sách đó chỉ là:

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

Hoặc không có tệp bên ngoài:

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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.