Como manipular um arquivo CSV com sed ou awk?

23

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

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

Eu tenho uma mesa grande com mais de 200 linhas e não estou familiarizada com isso sed.

Binoy Babu
fonte
1
Cross postado em AskUbuntu
enzotib
@enzotib você pode postar o link?
n0pe
@MaxMackie askubuntu.com/questions/88142/… . Não consigo encontrar um mod lá a essa hora, então sinalizei pedindo que eles migrassem, se quisessem; ele já tem uma resposta aceita, então não tenho certeza se eles terão
Michael Mrozek
@MichaelMrozek, hmmm o que geralmente acontece nessas situações? Simplesmente mantemos as duplicatas?
n0pe
1
A menos que você precise executar em um sistema que possua apenas ferramentas básicas disponíveis, consulte Existe uma ferramenta robusta de linha de comando para processar arquivos csv?
Gilles 'SO- stop be evil'

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 de pré e pós- filtragem podem cuidar disso. Os filtros mostrados abaixo exigem os personagens \x01, \x02, \x03, \x04para não aparecer em qualquer lugar em seus dados.

Aqui estão os filtros envolvidos em um awkdespejo de campo simples .

Nota: o campo cinco possui um layout de "campo entre aspas" inválido / incompleto, mas é benigno no final de uma linha (dependendo do analisador CSV). Mas, é claro, causaria resultados imprevisíveis e problemáticos se fosse trocada de sua posição atual de fim de linha .

Atualizar; user121196 apontou um erro 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\1\x03/g; s/,"/,\x02/; :MC; s/\x02([^\x03]*),([^\x03]*)/\x02\1\x04\2/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 postagem é 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\1\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\1\x04\2/g
    tMC
    s/^,//                # remove spurious leading delimiter
'
Peter.O
fonte
como você excluiria a enésima coluna com base nesse filtro?
user121196
@ user121196 - Como mencionado na sentença de abertura, esta resposta mostra uma maneira de tornar os dados CSV mais consistentes. substituindo termicamente uma vírgula incorporada por aspas por um caractere de token neutro ... e depois revertendo-a para uma vírgula após a movimentação / corte / exclusão. Novamente, como mencionado, a etapa mover / cortar / excluir é substituída por um simples despejo de campo do awk .
precisa saber é o seguinte
1
falha neste caso: "15111 N. Hayden Rd., Ste 160,", ""
user121196
@ user121196: Obrigado por apontar isso. Atualizei a resposta com uma correção.
precisa saber é o seguinte
15

Isso depende se o seu arquivo CSV usa vírgulas apenas para delimitadores ou se você tem loucura como:

campo um, "campo dois", campo três

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 exemplo. A maneira mais fácil é provavelmente usar cut, o que permite especificar um delimitador -de quais campos você deseja imprimir -f; isto diz para ele 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, você pode escrever uma expressão regular que corresponda aos primeiros n-1campos, o nth e o restante, e pular a saída do nth (aqui né 2, então o primeiro grupo corresponde ao 1tempo :) \{1\}:

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

Existem várias maneiras de fazer isso awk, nenhuma delas particularmente elegante. Você pode usar um forloop, mas lidar com a vírgula à direita é 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 substrgerar o campo 1 e depois usá-lo para extrair tudo após o campo 2:

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

Isso é irritante para colunas mais adiante

Duplicando uma coluna

Em sedesta é essencialmente a mesma expressão como antes, mas você também capturar a coluna de destino e incluir esse grupo várias vezes na substituição:

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

No awkcaminho do loop for, seria algo como (novamente ignorando a vírgula à direita):

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

O substrcaminho:

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

(tcdyl apresentou um método melhor em sua resposta )

Movendo uma coluna

Eu acho que a sedsolução segue naturalmente as outras, mas começa a ficar ridiculamente longa

Michael Mrozek
fonte
Essa é uma resposta carregada! +1 :)
jaypal singh
Ridiculamente longo? Pah !
Gilles 'SO- stop be evil'
12

awké a sua melhor aposta. awkimprime 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

Redirecionar 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 no formato awk

Pantera
fonte
Como é CSV, você também precisará BEGIN { FS=","; OFS=","; }.
1
Eu acho que até FS = OFS = "," funcionará.
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, onde 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, pode simplesmente usar

awk -F,

para definir seu campo como vírgula, em vez de um espaço (que é o padrão). Existem vários recursos on-line bons do awk, um dos quais eu listo como fonte abaixo.

Fonte para # 3

tcdyl
fonte
Eu não sei muito sobre awk, mas parece saída de espaço-separada, mesmo que o separador de campo é ,(o campo-separadores apenas controla como ele lida com a entrada)
Michael Mrozek
@ MichaelMrozek: sim, é a variável OFS awk que controla o separador de campos de saída.
enzotib
Sim, e como mencionei na minha resposta, você pode passar a opção -F para awk para alterar o delimitador (por exemplo, -F,)
tcdyl
0

Isso funcionará para excluir

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

Entrada

a b c d

Saída

a c d
Steven Penny
fonte