Elmord's Magic Valley

Computers, languages, and computer languages. Às vezes em Português, sometimes in English.

fgetcsv: A cautionary tale

2013-05-18 01:52 -0300. Tags: comp, prog, web, php, rant, em-portugues

Quando eu reescrevi o blog system, eu resolvi manter o índice de posts em um arquivo CSV e usar as funções fgetcsv e fputcsv para manipulá-lo. Minha intuição prontamente me disse "isso é PHP, tu vai te ferrar", mas eu não lhe dei importância. Afinal, provavelmente seria mais eficiente usar essas funções do que ler uma linha inteira e usar a explode para separar os campos. (Sim, eu usei um txt ao invés de um SQLite. Eu sou feliz assim, ok?)

Na verdade o que eu queria era manter o arquivo como tab-separated values, de maneira que ele fosse o mais fácil possível de manipular com as ferramentas convencionais do Unix (cut, awk, etc.). As funções do PHP aceitam um delimitador como argumento, então pensei eu que bastaria mudar o delimitador para "\t" ao invés da vírgula e tudo estaria bem. Evidentemente eu estava errado: um dos argumentos da fputcsv é um "enclosure", um caractere que deve ser usado ao redor de valores que contêm espaços (ou outras situações? who knows?). O valor padrão para a enclosure é a aspa dupla. Acontece que a fputcsv exige uma enclosure: não é possível passar uma string vazia, ou NULL, por exemplo, para evitar que a função imprima uma enclosure em volta das strings que considerar dignas de serem envoltas. Lá se vai meu tab-separated file bonitinho. Mas ok, não é um problema fatal.

A segunda curiosidade é que a fgetcsv aceita um argumento "escape", que diz qual é o caractere de escape (\ por default). Evidentemente, você tem que usar um caractere de escape; a possibilidade de ler um arquivo em um formato em que todos os caracteres exceto o delimitador de campo e o "\n" tenham seus valores literais é inconcebível. Mas ok, podemos setar o escape para um caractere não-imprimível do ASCII (e.g., "\1") e esquecer da existência dele. Acontece que a fputcsv não aceita um caractere de escape, logo você não tem como usar o mesmo caractere não-imprimível nas duas funções. WTF?

Na verdade, agora testando melhor (já que a documentação não nos conta muita coisa), aparentemente a fputcsv nunca produz um caractere de escape: se o delimitador aparece em um dos campos, ele é duplicado na saída (i.e., a"b vira a""b). Evidentemente, não há como eliminar esse comportamento. Mas então o que será que faz o escape character da fgetcsv?

# php -r 'while ($a = fgetcsv(STDIN, 999, ",", "\"", "\\"))
             { var_export($a); echo "\n"; }'
a,z
array (
  0 => 'a',
  1 => 'z',
)
a\tb,z
array (
  0 => 'a\\tb',
  1 => 'z',
)

Ok, o escape não serve para introduzir seqüências do tipo \t. Talvez para remover o significado especial de outros caracteres?

a\,b,z
array (
  0 => 'a\\',
  1 => 'b',
  2 => 'z',
)
a b,z
array (
  0 => 'a b',
  1 => 'z',
)
a\ b,z
array (
  0 => 'a\\ b',
  1 => 'z',
)

Muito bem. Como vimos, o escape character serve para, hã... hmm.

Mas o fatal blow eu tive hoje, olhando a lista de todos os posts do blog e constatando que o post sobre o filme π estava aparecendo com o nome vazio. Eis que:

# php -r '
    $h = fopen("entryidx.entries", "r");
    while ($a = fgetcsv($h, 9999, "\t"))
       if ($a[0]=="20120322-pi") var_export($a);'
array (
  0 => '20120322-pi',
  1 => 'π',
  2 => '2012-03-22 23:53 -0300',
  3 => 'film',
)

# LC_ALL=C php -r '
    $h = fopen("entryidx.entries", "r");
    while ($a = fgetcsv($h, 9999, "\t"))
       if ($a[0]=="20120322-pi") var_export($a);'
array (
  0 => '20120322-pi',
  1 => '',
  2 => '2012-03-22 23:53 -0300',
  3 => 'film',
)

A próxima versão do Blognir deverá usar fgets/explode.

UPDATE: Aparentemente o problema só ocorre quando um caractere não-ASCII aparece no começo de um campo. Whut?

UPDATE 2:

"a b","c d"
array (
  0 => 'a b',
  1 => 'c d',
)
"a"b","c"d"
array (
  0 => 'ab"',
  1 => 'cd"',
)
"a\"b","c d"
array (
  0 => 'a\\"b',
  1 => 'c d',
)

Comentários / Comments (5)

Marcus Aurelius, 2013-05-18 22:19:04 -0300 #

Como eu já disse por aí, até que Java não é tão ruim...
(sei lá como seria isso em Java, mas duvido que tenha tamanha bizarrice)


Vítor De Araújo, 2013-05-19 20:58:14 -0300 #

Pois. Java me parece só limitado e clumsy, mas não necessariamente mal-feito. :P


Marcus Aurelius, 2013-05-19 21:53:33 -0300 #

Pois é. Uma vez li por aí sobre a história da criação do Java (o título é The Long Strange Trip to Java). Teve bastante briga comparando com as outras linguagens, como Python e Beta (????), que tinham recursos legais e Java (na época Oak) não tinha. Mas os outros queriam terminar o projeto nas suas lifetimes.

Daí a linguagem ficou assim. Não sou fã de Java, mas cada vez que olho em volta, vejo como podia ser pior, hahahaha.

(mas ainda não me conformo que aprendi mais coisas legais sobre linguagens de programação com Lua e Python do que com Java)

Deve ser a terceira ou quarta vez que escrevo sobre isso. Que coisa. Isso tá realmente preso na minha cabeça atualmente.


Marcus Aurelius, 2013-05-19 22:04:52 -0300 #

Mas agora on-topic, que bizarrice é essa do "a"b","c"d"? Quer dizer, é tudo bizarro, mas esse me chamou a atenção...

O "a\"b","c d" também é estranho. Parece que o cara estava indo bem na implementação do escape mas teve que entregar o código de qualquer jeito e assim ficou.


Vítor De Araújo, 2013-05-19 22:27:19 -0300 #

Mais testes *parecem* indicar que o primeiro par de aspas ele interpreta como enclosure, e o resto ele deixa como aspa literal...

E pois é, aquele último está quase ok. Mas aparentemente a fgetcsv nunca remove os escapes da entrada...


Deixe um comentário / Leave a comment

Main menu

Recent posts

Recent comments

Tags

em-portugues (213) comp (148) prog (71) in-english (62) life (49) unix (38) pldesign (37) lang (32) random (28) about (28) mind (26) lisp (25) fenius (22) mundane (22) web (20) ramble (18) img (13) rant (12) hel (12) scheme (10) privacy (10) freedom (8) esperanto (7) music (7) lash (7) bash (7) academia (7) copyright (7) home (6) mestrado (6) shell (6) android (5) conlang (5) misc (5) emacs (5) latex (4) editor (4) etymology (4) php (4) worldly (4) book (4) politics (4) network (3) c (3) tour-de-scheme (3) security (3) kbd (3) film (3) wrong (3) cook (2) treta (2) poem (2) physics (2) x11 (2) audio (2) comic (2) lows (2) llvm (2) wm (2) philosophy (2) perl (1) wayland (1) ai (1) german (1) en-esperanto (1) golang (1) translation (1) kindle (1) pointless (1) old-chinese (1)

Elsewhere

Quod vide


Copyright © 2010-2024 Vítor De Araújo
O conteúdo deste blog, a menos que de outra forma especificado, pode ser utilizado segundo os termos da licença Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International.

Powered by Blognir.