Remover vírgula entre aspas apenas em um arquivo delimitado por vírgula

22

Eu tenho um arquivo de entrada delimitado por vírgulas ( , ). Existem alguns campos entre aspas duplas que contêm uma vírgula. Aqui está a linha de amostra

123,"ABC, DEV 23",345,534.202,NAME

Eu preciso remover todas as vírgulas que estão ocorrendo dentro das aspas duplas e também as aspas duplas. Assim, a linha acima deve ser analisada conforme mostrado abaixo

123,ABC DEV 23,345,534.202,NAME

Eu tentei o seguinte usando sed , mas não dando resultados esperados.

sed -e 's/\(".*\),\(".*\)/ /g'

Algum truque rápido com sed , awk ou qualquer outro utilitário unix, por favor?

    
por mtk 20.09.2012 / 10:21

7 respostas

28

Se as aspas estiverem equilibradas, você desejará remover as vírgulas entre todas as outras citações. Isso pode ser expresso em awk da seguinte forma:

awk -F'"' -v OFS='' '{ for (i=2; i<=NF; i+=2) gsub(",", "", $i) } 1' infile

Saída:

123,ABC DEV 23,345,534.202,NAME

Explicação

O -F" faz o awk separar a linha nos sinais de aspas duplas, o que significa que qualquer outro campo será o texto entre aspas. O loop for executa gsub , abreviação de substituto global, em todos os outros campos, substituindo vírgula ( "," ) por nada ( "" ). O 1 no final invoca o bloco de código padrão: { print $0 } .

    
por 20.09.2012 / 10:55
7

Existe uma resposta boa , usando sed apenas uma vez com um loop :

echo '123,"ABC, DEV 23",345,534,"some more, comma-separated, words",202,NAME'|
  sed ':a;s/^\(\([^"]*,\?\|"[^",]*",\?\)*"[^",]*\),/ /;ta'
123,"ABC  DEV 23",345,534,"some more  comma-separated  words",202,NAME

Explicação:

  • :a; é um rótulo para o segmento furter
  • s/^\(\([^"]*,\?\|"[^",]*",\?\)*"[^",]*\),/ / pode conter 3 partes fechadas
    • primeiro a segunda: [^"]*,\?\|"[^",]*",\? correspondência para uma string que não contenha aspas duplas, talvez seguida de um coma ou uma string entre duas aspas duplas, sem coma e talvez seguida de coma.
    • que a primeira parte RE é composta por tantas repetições da parte 2 descrita anteriormente, seguidas por 1 aspas duplas e alguns caracteres, mas sem aspas duplas, nem comas.
    • A primeira parte do RE a ser seguida por um coma.
    • Nota, o resto da linha não precisa ser tocado
  • ta fará um loop para :a se o comando anterior s/ tiver feito alguma alteração.
por 22.11.2012 / 09:28
5

Uma solução geral que também pode manipular várias vírgulas entre aspas balanceadas precisa de uma substituição aninhada. Eu implemento uma solução em perl, que processa cada linha de uma dada entrada e só substitui vírgulas em cada par de aspas:

perl -pe 's/ "  (.+?  [^\])  "               # find all non escaped 
                                              # quoting pairs
                                              # in a non-greedy way

           / ($ret = $1) =~ (s#,##g);         # remove all commas within quotes
             $ret                             # substitute the substitution :)
           /gex'

ou resumindo

perl -pe 's/"(.+?[^\])"/($ret = $1) =~ (s#,##g); $ret/ge'

Você pode enviar o texto que deseja processar para o comando ou especificar o arquivo de texto a ser processado como último argumento de linha de comando.

    
por 20.09.2012 / 10:56
3

Eu usaria uma linguagem com um analisador de CSV adequado. Por exemplo:

ruby -r csv -ne '
  CSV.parse($_) do |row|
    newrow = CSV::Row.new [], []
    row.each {|field| newrow << field.delete(",")}
    puts newrow.to_csv
  end
' < input_file
    
por 20.09.2012 / 16:29
3

Suas segundas citações estão equivocadas:

sed -e 's/\(".*\),\(.*"\)/ /g'

Além disso, o uso de expressões regulares tende a corresponder à parte mais longa possível do texto, o que significa que isso não funcionará se você tiver mais de um campo entre aspas na string.

Uma maneira que lida com vários campos entre aspas no sed

sed -e 's/\(\"[^",]\+\),\([^",]*\)/ /g' -e 's/\"//g'

Esta é também uma maneira de resolver isso, no entanto, com a entrada que pode conter mais de uma vírgula por campo citado, a primeira expressão no sed teria que ser repetida tantas vezes quanto o conteúdo máximo de vírgula em um único campo, ou até que não altere a saída.

A execução de sed com mais de uma expressão deve ser mais eficiente do que vários processos sed executados e um "tr" sendo executado com canais abertos.

No entanto, isso pode ter consequências indesejadas se a entrada não estiver formatada corretamente. ou seja, citações aninhadas, citações não finalizadas.

Usando o exemplo em execução:

echo '123,"ABC, DEV 23",345,534,"some more, comma-separated, words",202,NAME' \
| sed -e 's/\(\"[^",]\+\),\([^",]*\)/ /g' \
-e 's/\(\"[^",]\+\),\([^",]*\)/ /g' -e 's/\"//g'

Saída:

123,ABC  DEV 23,345,534,some more  comma-separated  words,202,NAME
    
por 20.09.2012 / 10:28
2

Em perl - você pode usar Text::CSV para analisar isso e fazer isso trivialmente:

#!/usr/bin/env perl
use strict;
use warnings;

use Text::CSV; 

my $csv = Text::CSV -> new();

while ( my $row = $csv -> getline ( \*STDIN ) ) {
    #remove commas in each field in the row
    $_ =~ s/,//g for @$row;
    #print it - use print and join, rather than csv output because quotes. 
    print join ( ",", @$row ),"\n";
}

Você pode imprimir com Text::CSV , mas ele tende a preservar as cotações, se você fizer isso. (Embora, eu sugiro - em vez de extrair cotações para sua saída, você poderia apenas analisar usando Text::CSV em primeiro lugar).

    
por 17.11.2015 / 18:50
0

Eu criei uma função para percorrer todos os caracteres da string.
Se o caractere for uma cotação, o cheque (b_in_qt) será marcado como verdadeiro.
Enquanto b_in_qt for true, todas as vírgulas serão substituídas por um espaço.
b_in_qt é definido como falso quando a próxima vírgula é encontrada.

FUNCTION f_replace_c (str_in  VARCHAR2) RETURN VARCHAR2 IS
str_out     varchar2(1000)  := null;
str_chr     varchar2(1)     := null;
b_in_qt     boolean         := false;

BEGIN
    FOR x IN 1..length(str_in) LOOP
      str_chr := substr(str_in,x,1);
      IF str_chr = '"' THEN
        if b_in_qt then
            b_in_qt := false;
        else
            b_in_qt := true;
        end if;
      END IF;
      IF b_in_qt THEN
        if str_chr = ',' then
            str_chr := ' ';
        end if;
      END IF;
    str_out := str_out || str_chr;
    END LOOP;
RETURN str_out;
END;

str_in := f_replace_c ("blue","cat,dog,horse","",yellow,"green")

RESULTS
  "blue","cat dog horse","",yellow,"green"
    
por 17.11.2015 / 18:21