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 mesa grande com mais de 200 linhas e não estou familiarizada com isso sed
.
Como posso fazer o seguinte em um arquivo CSV usando sed
ou awk
?
Eu tenho uma mesa grande com mais de 200 linhas e não estou familiarizada com isso sed
.
Respostas:
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
, \x04
para não aparecer em qualquer lugar em seus dados.
Aqui estão os filtros envolvidos em um awk
despejo 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
'
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:
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 -d
e 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-1
campos, o n
th e o restante, e pular a saída do n
th (aqui n
é 2, então o primeiro grupo corresponde ao 1
tempo :) \{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 for
loop, 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 substr
gerar 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
Em sed
esta é 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 awk
caminho 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 substr
caminho:
$ awk -F, '{print $1 "," $2 "," substr($0, length($1)+2)}' /path/to/your/file
(tcdyl apresentou um método melhor em sua resposta )
Eu acho que a sed
solução segue naturalmente as outras, mas começa a ficar ridiculamente longa
awk
é a 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
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.
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
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)
Isso funcionará para excluir
awk '{$2="";$0=$0;$1=$1}1'
Entrada
a b c d
Saída
a c d