Como manipular um arquivo CSV com sed ou awk?

23

Como posso fazer o seguinte em um arquivo CSV usando sed ou awk ?

  • Excluir uma coluna
  • Duplicar uma coluna
  • Mover uma coluna

Eu tenho uma tabela grande com mais de 200 linhas e não estou familiarizado com sed .

    
por Binoy Babu 16.12.2011 / 02:41

5 respostas

7

Além de como cortar e reorganizar os campos (abordados nas outras respostas), há a questão dos campos CSV peculiares.

Se seus dados se enquadram nessa categoria "peculiar", um pouco da filtragem pré e postagem pode cuidar disso. Os filtros mostrados abaixo exigem que os caracteres \x01 , \x02 , \x03 , \x04 não apareçam em nenhum lugar dos seus dados.

Aqui estão os filtros agrupados em torno de um simples dump de campo awk .

Observação: campo-cinco tem um layout "campo entre aspas" inválido / incompleto, mas é benigno no final de uma linha (dependendo do analisador de CSV) . Mas, é claro, isso causaria resultados inesperados problemáticos se fosse para ser trocado de sua posição atual fim de linha .

Atualização; user121196 apontou um bug quando uma vírgula precede uma citação à direita. Aqui está a correção.

Os dados

cat <<'EOF' >file
field one,"fie,ld,two",field"three","field,\",four","field,five
"15111 N. Hayden Rd., Ste 160,",""
EOF

O código

sed -r 's/^/,/; s/\"/\x01/g; s/,"([^"]*)"/,\x02\x03/g; s/,"/,\x02/; :MC; s/\x02([^\x03]*),([^\x03]*)/\x02\x04/g; tMC; s/^,// ' file |
  awk -F, '{ for(i=1; i<=NF; i++) printf "%s\n", $i; print NL}' |
    sed -r 's/\x01/\"/g; s/(\x02|\x03)/"/g; s/\x04/,/g' 

A saída:

field one
"fie,ld,two"
field"three"
"field,\",four"
"field,five

"15111 N. Hayden Rd., Ste 160,"
""

Aqui está o pré filtro , expandido com comentários.
O filtro de postagens é apenas uma reversão de \x01 . \x02 , \x03 , \x04

sed -r '
    s/^/,/                # add a leading comma delimiter
    s/\"/\x01/g          # obfuscate escaped quotation-mark (\")
    s/,"([^"]*)"/,\x02\x03/g    # obfuscate quotation-marks
    s/,"/,\x02/           # when no trailing quote on last field  
    :MC                   # obfuscate commas embedded in quotes
    s/\x02([^\x03]*),([^\x03]*)/\x02\x04/g
    tMC
    s/^,//                # remove spurious leading delimiter
'
    
por 16.12.2011 / 07:59
14

Isso depende de o arquivo CSV usar vírgulas apenas para delimitadores ou se você tiver uma loucura como:

field one,"field,two",field three

Isso pressupõe que você esteja usando um arquivo CSV simples:

Removendo uma coluna

Você pode se livrar de uma única coluna de várias maneiras; Eu usei a coluna 2 como um exemplo. A maneira mais fácil é usar cut , que permite especificar um delimitador -d e quais campos você deseja imprimir -f ; isso diz para dividir em vírgulas e campo de saída 1 e campos 3 até o final:

$ cut -d, -f1,3- /path/to/your/file

Se você realmente precisar usar sed , poderá gravar uma expressão regular que corresponda aos primeiros n-1 fields, o n th campo e o restante e pule a saída de n th (aqui n é 2, então o primeiro grupo é correspondido 1 time: \{1\} ):

$ sed 's/\(\([^,]\+,\)\{1\}\)[^,]\+,\(.*\)//' /path/to/your/file

Existem várias maneiras de fazer isso em awk , nenhuma delas é particularmente elegante. Você pode usar um loop for , mas lidar com a vírgula final é uma dor; ignorando que seria algo como:

$ awk -F, '{for(i=1; i<=NF; i++) if(i != 2) printf "%s,", $i; print NL}' /path/to/your/file

Acho mais fácil produzir o campo 1 e, em seguida, usar substr para retirar tudo depois do campo 2:

$ awk -F, '{print $1 "," substr($0, length($1)+length($2)+3)}' /path/to/your/file

Isso é irritante para colunas ainda mais

Duplicando uma coluna

Em sed , esta é essencialmente a mesma expressão de antes, mas você também captura a coluna de destino e inclui esse grupo várias vezes na substituição:

$ sed 's/\(\([^,]\+,\)\{1\}\)\([^,]\+,\)\(.*\)//' /path/to/your/file

Em awk , a maneira for loop seria algo como (novamente ignorando a vírgula final):

$ awk -F, '{
for(i=1; i<=NF; i++) {
    if(i == 2) printf "%s,", $i;
    printf "%s,", $i
}
print NL
}' /path/to/your/file

A maneira substr :

$ awk -F, '{print $1 "," $2 "," substr($0, length($1)+2)}' /path/to/your/file

(o tcdyl veio com um método melhor em sua resposta )

Mover uma coluna

Acho que a solução sed segue naturalmente dos outros, mas começa a ficar ridiculamente longa

    
por 16.12.2011 / 03:38
12

awk é sua melhor aposta. awk imprime campos por número, então ...

awk 'BEGIN { FS=","; OFS=","; } {print $1,$2,$3}' file

Para remover uma coluna, não a imprima:

 awk 'BEGIN { FS=","; OFS=","; } {print $1,$3}' file

Para alterar a ordem:

awk 'BEGIN { FS=","; OFS=","; } {print $3,$1,$2}' file

Reorientar para um arquivo de saída.

awk 'BEGIN { FS=","; OFS=","; } {print $3,$1,$2}' file > output.file

awk também pode formatar a saída.

Saída do formato Awk

    
por 16.12.2011 / 03:08
5

Dado um arquivo delimitado por espaço no seguinte formato:

1 2 3 4 5

Você pode remover o campo 2 com o awk da seguinte forma:

awk '{ sub($2,""); print}' file

que retorna

1  3 4 5

Substitua a coluna 2 pela coluna n quando apropriado.

Para duplicar a coluna 2,

awk '{ col = $2 " " $2; $2 = col; print }' file

que retorna

1 2 2 3 4 5

Para alternar as colunas 2 e 3,

awk '{temp = $2; $2 = $3; $3 = temp; print}'

que retorna

1 3 2 4 5

o awk geralmente é muito bom em lidar com o conceito de campos . Se você está lidando com um CSV, e não com um arquivo delimitado por espaço, você pode simplesmente usar

awk -F,

para definir seu campo como uma vírgula, em vez de um espaço (que é o padrão). Há uma série de bons recursos online, um dos quais eu listo como uma fonte abaixo.

Fonte para # 3

    
por 16.12.2011 / 03:21
0

Isso funcionará para excluir

awk '{$2="";$0=$0;$1=$1}1'

Entrada

a b c d

Saída

a c d
    
por 26.12.2014 / 07:01

Tags