Transformando linhas separadas em uma lista separada por vírgula com entradas entre aspas


15

Tenho os seguintes dados (uma lista de pacotes R analisados ​​a partir de um arquivo Rmarkdown), que desejo transformar em uma lista que posso passar para o R para instalar:

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

Quero transformar a lista em uma lista do formulário:

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

Atualmente, tenho um pipeline de bash que vai do arquivo bruto para a lista acima:

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

Quero adicionar uma etapa para transformar as novas linhas na lista separada por vírgula. Eu tentei adicionar tr '\n' '","', o que falha. Eu também tentei várias das seguintes respostas de estouro de pilha, que também falham:

Isso produz library(stringr)))phics)como resultado.

Isso produz ,%como resultado.

Esta resposta (com o -isinalizador removido) produz saída idêntica à entrada.


Os delimitadores precisam ter espaço de vírgula ou somente a vírgula é aceitável?
steeldriver

Ou está bom, mas preciso de um caractere de citação ao redor da string, 'ou ".
FBT


Sou o primeiro a perceber que os dados de entrada e o script para processá-los são completamente incompatíveis. Não haverá saída.
Ctrl-alt-delor

O script que listei é como eu gero os dados de entrada. Alguém pediu. Os dados de entrada reais seria algo parecido com isso . Observe que o Github altera a formatação para remover as novas linhas.
FBT

Respostas:


19

Você pode adicionar aspas com sed e mesclar linhas com colar , assim:

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

Se você estiver executando um sistema baseado em coreutils GNU (ou seja, Linux), poderá omitir o final '-'.

Se você inserir dados com terminações de linha no estilo DOS (como @phk sugeriu), poderá modificar o comando da seguinte maneira:

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

1
No MacOS (e talvez outros), você precisará incluir um traço para indicar que a entrada é do stdin, e não do arquivo:sed 's/^\|$/"/g'|paste -sd, -
cherdt

É verdade que a versão da pasta "coreutils" aceita os dois formulários, mas "-" é mais POSIX. Valeu !
Zeppelin

2
Ou apenas com sed:sed 's/.*/"&"/;:l;N;s/\n\(.*\)$/, "\1"/;tl'
Digital Trauma

1
@fbt A nota que agora adicionei no final da minha resposta se aplica aqui também.
Phk #

1
@DigitalTrauma - não é realmente uma boa ideia; isso seria muito lento (pode até travar com arquivos enormes) - veja as respostas para o QI vinculado no meu comentário no Q aqui; O legal é usar pastesó;)
don_crissti

8
Usando awk:
awk 'BEGIN { ORS="" } { print p"'"'"'"$0"'"'"'"; p=", " } END { print "\n" }' /path/to/list
Alternativa com menos escape de shell e, portanto, mais legível:
awk 'BEGIN { ORS="" } { print p"\047"$0"\047"; p=", " } END { print "\n" }' /path/to/list
Resultado:
'd3heatmap', 'data.table', 'ggplot2', 'htmltools', 'htmlwidgets', 'metricsgraphics', 'networkD3', 'plotly', 'reshape2', 'scales', 'stringr'
Explicação:

O awkscript em si sem todo o escape é BEGIN { ORS="" } { print p"'"$0"'"; p=", " } END { print "\n" }. Depois de imprimir a primeira entrada, a variável pé definida (antes disso, é como uma string vazia). Com essa variável, pcada entrada (ou in awk-speak: record ) é prefixada e adicionalmente impressa com aspas simples. A awkvariável separadora do registro de saída ORSnão é necessária (já que o prefixo está fazendo isso por você), portanto, ela deve estar vazia no momento BEGIN. Ah, e podemos usar nosso arquivo ENDcom uma nova linha (por exemplo, para que funcione com outras ferramentas de processamento de texto); caso isso não seja necessário, a parte comEND e tudo o que está depois (dentro das aspas simples) pode ser removido.

Nota

Se você possui terminações de linha no estilo Windows / DOS ( \r\n), primeiro é necessário convertê-las para o estilo UNIX ( \n). Para fazer isso, você pode colocar tr -d '\015'no início do seu pipeline:

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

(Supondo que você não tenha utilidade para \rs no seu arquivo. Pressuposto muito seguro aqui.)

Como alternativa, basta executar dos2unix /path/to/input.listuma vez para converter o arquivo no local.


Quando executo esse comando, recebo ', 'stringr23aphicscomo saída.
FBT

@fbt Veja minha nota mais recente.
Phd #

2
print p"'"'"'"$0"'"'"'"; p=", "- citações sagradas, Batman!
wchargin

Eu sei, certo‽ :) Pensei em mencionar que, em muitos shells, a impressão p"'\''"$0"'\''";também funcionaria (embora não seja POSIXy) ou, alternativamente, usando bashas seqüências de citação C de C ( $'') mesmo que apenas print p"\'"$0"\'";(pode ser necessário duplicar outras barras invertidas), mas há já o outro método usando awko caractere de escape.
Phd #

Uau, não acredito que você descobriu isso. Obrigado.
FBT

6

Como mostra a resposta vinculada de @ don_crissti , a opção de colar é incrivelmente rápida - a tubulação do kernel do linux é mais eficiente do que eu pensaria se não tivesse tentado agora. Notavelmente, se você puder ficar feliz com uma única vírgula separando os itens da lista em vez de uma vírgula + espaço, um pipeline de colagem

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

é mais rápido que um flexprograma razoável (!)

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

Mas se apenas um desempenho decente for aceitável (e se você não estiver executando um teste de estresse, não poderá medir diferenças de fator constante, são todos instantâneos) e deseja flexibilidade com seus separadores e um razoável -liner-y-ness,

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

é o seu bilhete. Sim, parece ruído de linha, mas o H;1h;$!d;xidioma é o caminho certo para absorver tudo, uma vez que você pode reconhecer que a coisa toda fica realmente fácil de ler, é s/.*/'&'/seguida por um slurp e a s/\n/, /g.


edit: contornando o absurdo, é bastante fácil usar o flex para superar tudo o mais, basta dizer ao stdio que você não precisa da sincronização multithread / signalhandler integrada:

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

e sob estresse 2-3 vezes mais rápido que os pipelines de pasta, que são pelo menos cinco vezes mais rápidos que tudo o resto.


1
(paste -d\ \'\' /dev/null /dev/null - /dev/null | paste -sd, -) <infile | cut -c2-faria vírgula espaço + @ praticamente a mesma velocidade embora, como você observou, não é realmente flexível, se você precisar de alguma seqüência de fantasia como separador
don_crissti

Esse flexmaterial é bem legal, cara ... esta é a primeira vez que vejo alguém postando flexcódigo neste site ... grande votação! Poste mais dessas coisas.
don_crissti

@don_crissti Thanks! Vou procurar boas oportunidades, sed / awk / whatnot geralmente são melhores opções apenas pelo valor da conveniência, mas muitas vezes há uma resposta flexível muito fácil também.
jthill

4

Perl

One-liner do Python:

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

Funciona de maneira simples - redirecionamos input.txt para o stdin usando o <operador do shell , lemos cada linha em uma lista com a .strip()remoção de novas linhas e a repr()criação de uma representação entre aspas de cada linha. A lista é então unida em uma grande sequência via .join()função, com ,o separador

Como alternativa, poderíamos usar +para concatenar aspas para cada linha despojada.

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

Perl

Essencialmente, a mesma idéia de antes: leia todas as linhas, descasque a nova linha, coloque aspas simples, coloque tudo em array @cvs e imprima os valores da matriz unidos por vírgulas.

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

'd3heatmap', 'data.table', 'ggplot2', 'htmltools', 'htmlwidgets', 'metricsgraphics', 'networkD3', 'plotly', 'remodelar2', 'escalas', 'stringr'


IIRC, os pythons joindevem ser capazes de levar um iterador, portanto, não há necessidade de materializar o loop stdin em uma lista
iruvar

@iruvar Sim, exceto observe a saída desejada do OP - eles querem que cada palavra seja citada, e precisamos remover as novas linhas finais para garantir que a saída seja uma linha. Você tem uma idéia de como fazer isso sem uma compreensão da lista?
precisa saber é o seguinte

3

Eu acho que o seguinte deve funcionar muito bem, supondo que seus dados estejam no texto do arquivo

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

Vamos usar matrizes que têm a substituição no frio:

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

A saída do script deve ser a seguinte:

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

Eu acredito que era isso que você estava procurando?


1
Ótima solução. Mas, embora o OP não tenha solicitado explicitamente bashe embora seja seguro supor que alguém possa usá-lo (afinal o AFAIK é o shell mais usado), ele ainda não deve ser tomado como garantido. Além disso, existem partes em que você poderia fazer um trabalho melhor ao citar (colocar aspas duplas). Por exemplo, embora seja improvável que os nomes dos pacotes tenham espaços neles, ainda é uma boa convenção citar variáveis ​​em vez de não, você pode executar o shellcheck.net sobre ele e ver as notas e explicações nele.
Phd #

2

Muitas vezes, tenho um cenário muito semelhante: copio uma coluna do Excel e quero converter o conteúdo em uma lista separada por vírgula (para uso posterior em uma consulta SQL como ... WHERE col_name IN <comma-separated-list-here>).

Isto é o que eu tenho no meu .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
}

Em seguida, corro lbl("linha por linha") na linha cmd que aguarda a entrada, colo o conteúdo da área de transferência, pressione <C-D>e a função retorna a entrada cercada (). Parece assim:

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

(Não me lembro por que coloquei o dos2unix aqui, provavelmente porque isso geralmente causa problemas na configuração da minha empresa.)


1

Algumas versões do sed agem um pouco diferente, mas no meu mac, eu posso lidar com tudo, exceto o "uniq" no 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
}
'

Infelizmente, para corrigir a parte única, é necessário fazer algo como:

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

--Paulo


2
Bem-vindo ao Unix.stackexchange! Eu recomendo que você faça o passeio .
Stephen Rauch

0

É engraçado que, para usar uma lista em texto sem formatação de pacotes R para instalá-los no R, ninguém propôs uma solução usando essa lista diretamente no R, mas lute com bash, perl, python, awk, sed ou o que quer que seja para colocar aspas e vírgulas no Lista. Isso não é necessário e, além disso, não resolve como a entrada e o uso da lista transformada em R.

Você pode simplesmente carregar o arquivo de texto sem formatação (dito packages.txt) como um quadro de dados com uma única variável, que você pode extrair como um vetor, diretamente utilizável install.packages. Portanto, converta-o em um objeto R utilizável e instale essa lista:

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

Ou sem um arquivo externo:

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)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.