Elmord's Magic Valley

Software, lingüística, mitologia nórdica e rock'n'roll

fgetcsv: A cautionary tale

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

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',
)

5 comentários

Um aviso de utilidade pública

2013-05-16 15:27 -0300. Tags: home, img

Isto aqui, tanto quanto eu pude determinar (especialmente depois de ter assistido a um desses andando), é um caracol más pequeño del mundo:

[Imagem de um caracol más pequeño del mundo]

Se você não eliminá-los, eles vão crescer, tomar conta da sua casa, rastejar sobre seus pertences e rir na cara da gravidade. Como se não bastasse, eles também produzirão outros caracóis más pequeños del mundo, que em tempo farão a mesma coisa.

#ficadica.

2 comentários

Noun-centric vs. verb-centric OO

2013-05-11 01:36 -0300. Tags: comp, prog, lisp, pldesign

As linguagens orientadas a objetos convencionais são o que podemos chamar de noun-centric: as classes são a "unidade estrutural" mais importante, e os métodos (verbos) pertencem a classes (nomes). Um método é chamado sobre exatamente um objeto, e a escolha de que código será executado para a chamada depende da classe do objeto sobre o qual o método é chamado. A sintaxe objeto.método(args) reflete essa idéia.

Algumas outras linguagens, como Common Lisp e Dylan, entretanto, seguem um modelo "verb-centric": os métodos são definidos fora das classes. Métodos de mesmo "nome" (onde nome = símbolo + pacote ao qual pertence) são agrupados sob uma mesma generic function. As classes de todos os argumentos da generic function são consideradas na hora de escolher que código será executado. Em Common Lisp, a sintaxe (método arg1 arg2...) reflete essa ausência de preferência por um dos argumentos e a existência da função como algo externo a qualquer classe. (Não lembro mais de onde saíram os termos "noun-centric" e "verb-centric" para descrever essa diferença, mas eles não são invenção minha.)

As conseqüências na prática são várias. Uma delas é que no modelo verbocêntrico é possível criar novos métodos para classes existentes sem ter que modificá-las. Por exemplo, se você quiser criar um método novo para trabalhar com strings, você pode defini-lo e usá-lo como qualquer outro método sobre strings, ao invés de criar uma distinção artificial entre métodos nativos da classe String ("foo".method(42)) e métodos definidos somewhere else que trabalham com strings (RandomClass.method("foo", 42)). (Ruby, uma linguagem nominocêntrica, resolve esse problema permitindo "redefinições parciais" de classes. Essa solução, entretanto, tem o problema de que todos os métodos sobre uma classe compartilham o mesmo namespace, i.e., se dois arquivos diferentes quiserem definir method sobre uma mesma classe, as definições conflitarão. Em Common Lisp, cada method estaria em seu próprio pacote, o que evita esse problema.)

Outra vantagem do modelo verbocêntrico é que evitam-se decisões arbitrárias quanto a em que classe se deve incluir um método. Por exemplo, imagine que queiramos definir um método (match string regex), que retorna a primeira ocorrência de regex em string. Esse método vai na classe String, ou em RegEx? E se quisermos procurar por outras coisas além de regexes dentro da string (e.g., outras strings)? E se quisermos procurar regexes em coisas que não são strings (e.g., streams)? No modelo verbocêntrico, essa decisão simplesmente não existe; basta definir match para todas as combinações de tipos de argumentos possíveis.

Uma terceira conseqüência desse modelo é que o conceito de "interface" é desnecessário: "implementar uma interface" consiste em especializar os métodos desejados para a classe em questão. (De certa forma, pode-se imaginar cada generic function como uma interface de um método só, e cada definição de método para a generic function como a implementação da interface para uma combinação de classes. De certa forma, pode-se imaginar muitas coisas.) Uma desvantagem disso é que se perde a garantia estática provida pelas interfaces de que de uma dada classe implementa um conjunto de métodos relacionados. (Garantias estáticas não são exatamente um ponto forte do Common Lisp.)

No modelo nominocêntrico, é possível descrever chamadas de métodos em termos de troca de mensagens: quando escrevemos obj.foo(23, 42), podemos pensar que estamos enviando a mensagem foo(23, 42) para o objeto obj. De fato, em Smalltalk, uma das primeiras linguagens orientadas a objeto, as mensagens são objetos de primeira classe, e é possível fazer algumas coisas interessantes com elas (como por exemplo repassá-las para outros objetos, ou definir um método doesNotUnderstand que trata todas as mensagens que uma classe não reconhece). O modelo de troca de mensagens também é interessante em implementações de objetos distribuídos: nesse caso, uma chamada de método sobre um objeto remoto é de fato uma mensagem enviada pela rede para a máquina onde o objeto se encontra. Uma desvantagem do modelo verbocêntrico é que o a descrição em termos de troca de mensagens não é mais aplicável: um método é chamado sobre múltiplos objetos, e portanto não há um "receptor" da mensagem.

quem diga que o CLOS (Common Lisp Object System) não é de fato orientação a objetos, mas sim um paradigma diferente que é capaz de expressar orientação a objetos e (um bocado de) outras coisas (muitas delas não abordadas neste post, tais como before/after methods (que lembram programação orientada a aspectos) e method combinations). Hoje em dia eu me vejo inclinado a concordar com essa posição, já que o CLOS foge da idéia de objetos como caixinhas fechadas manipuladas apenas através dos métodos expostos pela classe correspondente. Encapsulamento é possível em Common Lisp (basta não exportar os nomes dos slots (a.k.a. propriedades, atributos, membros) no pacote onde foram definidos), mas a noção de objeto = dados + operações se perde, de certa forma. Os objetos são apenas os dados, e as operações somos nós estão contidas nas generic functions / métodos.

2 comentários

Incomparável

2013-05-10 18:39 -0300. Tags: random, img

Incomparável como seu carinho

Incomparável... como seu carinho.

2 comentários

Twitter via linha de comando

2013-05-07 14:27 -0300. Tags: comp, unix, web, about

Por falta de coisa mais interessante para fazer, e já que RSS não é exatamente a tecnologia da modinha, estou disponibilizando experimentalmente um feed do blog no Twitter. A continuidade do "serviço" está sujeita à existência de usuários. [Update: Pensando melhor, provavelmente todo o mundo que tem interesse em seguir blogs usa RSS. Enfim, o feed está aí, por enquanto.]

A parte interessante da história é que eu descobri um bocado de clientes de linha de comando do Twitter no processo. Dos que eu experimentei, o que melhor me satisfez foi o t. (Eu experimentei mais outros dois clientes: o TTYtter, um cliente interativo pra lá de bizarro, mas com mil features e aparentemente fácil de estender; e o twidge, que aparentemente não suporta UTF-8. Existem dúzias de outros clientes, como uma pesquisa no Google revela.)

Os poderes mágicos do t derivam do fato de ele ser particularmente conveniente de usar em scripts. Um exemplo extraído da documentação:

Favorite the last 10 tweets that mention you

t mentions -n 10 -l | awk '{print $1}' | xargs t favorite

É possível instalar o t pelo RubyGems, através do comando gem install t. Antes de instalá-lo, certifique-se de que você tem instalado o Ruby e o RubyGems (pacotes ruby, ruby-dev e rubygems no Debian/Ubuntu; não ter o ruby-dev é um problema comum).

Uma vez instalado, é necessário executar t authorize, para realizar o processo de registro da aplicação no Twitter e de autorização do acesso da aplicação à sua conta. Você pode executar t sem argumentos para ver uma lista dos comandos disponíveis. Para mais informações, dê uma olhada no README na página do projeto.

(Quem me contou foi essa página.)

4 comentários

Culinária para os Totalmente Perdidos #1: Miojo com molho de atum/guisado

2013-05-06 05:31 -0300. Tags: cook, home

Isso aqui é tão simples que praticamente não conta como receita, mas dada a freqüência com que eu ouço gente dizer que come o Miojo puro (ou ainda, com aquele temperinho maligno (shudder)), talvez este post possa ser útil.

Ingredientes

O molho

Atum edition: Abra a lata de atum e despeje fora o óleo/água. Despeje o atum em uma panela e deixe cozinhar por cerca de um minuto, mexendo ocasionalmente.

Guisado edition: Despeje um pouquinho assim de óleo (cerca de 5 colheres de sopa) em uma panela e deixe aquecer por uns 30 segundos. Despeje o guisado e deixe cozinhar por uns três minutos, ou até que a carne não esteja mais vermelha, mexendo ocasionalmente.

Em ambos os casos: Feitos os passos acima, despeje o molho pronto sobre a coisa toda e misture, e deixe cozinhar por cerca de dois minutos, mexendo ocasionalmente. Enquanto o molho cozinha, você pode adicionar o orégano (umas três colheres de chá, ou o quanto seu coração disser), se desejar. (Na minha experiência, o orégano fica melhor com atum do que com guisado.) Está feito.

Rendimento: o suficiente para duas porções de massa (veja adiante). Mesmo que você só vá fazer uma porção de massa, se optar pelo atum, é melhor fazer a quantidade "inteira" e guardar o que sobrar, pois atum estraga depois de aberto se não for cozido.

A massa

Use de ¾ a 1 tablete de massa, dependendo do grau de fome, para cada pessoa. (Para referência: 1 tablete = 125g; 1 miojinho = 80g). Ferva água suficiente para submergir o miojo. Despeje o miojo na água e deixe cozinhar por 5 minutos (sim, cinco; aquela história de "pronto em 3 minutos" funciona para o miojinho, mas nem tanto para o miojão), ou até que a consistência lhe agrade. Despeje a água fora e misture a massa com o molho. Divirta-se.

Observações

Você pode fazer a massa e o molho em paralelo e ganhar tempo, ou fazer primeiro a massa e depois o molho e ganhar uma panela a menos para lavar.

Há quem diga que não se deve derramar o óleo do atum na pia, pois provoca entupimento e caos e destruição. Ao invés disso, deve-se coletá-lo e entregá-lo em um daqueles lugares míticos em que se coleta óleo. (Imagino eu que derramar o óleo em uma garrafa e colocá-lo no lixo também seja uma alternativa razoável.)

2 comentários

#123

2013-05-02 23:20 -0300. Tags: random, life, mind, ramble

Caro mundo,

Ao invés de estar aproveitando meu tempo de uma maneira produtiva, resolvi passar o final do meu dia lendo o Hyperbole and a Half desde o primeiro post. Por mais improdutivo que seja, não posso dizer que foi uma má decisão. Além disso, ler certas coisas me fez pensar que morar nessa casa com o chão todo torto e que está começando a ficar úmido com a proximidade do inverno e que em breve vai começar a verter água, ao mesmo tempo em que caracóis começam a invadir a casa, até que não é algo tão ruim assim. O tempo está com cara de tempestade iminente, o que significa que meu 3G vai começar a se arrastar como os recém-mencionados caracóis, o que pode dificultar minha continuada leitura do recém-mencionado blog, mas enfim. (A concretização da tempestade, por sua vez, pode levar à falta de luz na rua em que se localiza a recém-mencionada residência pela qual eu acabo de expressar renovado apreço, rua esta com uma infraestrutura elétrica menos que invejável. Mas enfim. O tempo está bonito, pelo menos.)

(As a sidenote, se por acaso alguém estiver preocupado com o sumiço da Allie no final de 2011, saiba que ela deu sinais de vida no Reddit em março de 2012. Eu fiquei contente quando encontrei isso, anyway. [Update: She's back! Good timing, huh?])

(Como se diz "sidenote" em português? "Preterlóquio?" (Estou surpreso pela total ausência de resultados na minha busca no Google por "preterlóquio". Considerem a palavra criada (ainda que, talvez, não necessariamente com o mesmo exato sentido).))

Mas não é sobre nada disso que eu vim falar hoje. O que eu vim falar aqui originalmente é sobre o design de shells. Lembram que eu escrevi um post quatro meses atrás dizendo que estava querendo escrever um shell? Pois bem, obviamente eu não fiz isso, mas nesse meio tempo eu tive idéias. (Se me pagassem para ter idéias de software de aplicabilidade questionável eu estaria com a vida ganha. Ou não.) Na verdade eu agora fui olhar o respectivo post e descobri que ele é muito maior do que eu me lembrava, de tal maneira que eu já nem sei se vale a pena escrever outro post sobre o assunto. Quem sabe assim eu resolvo criar vergonha na cara e de fato implementar as idéias, ao invés de ficar discutindo o quão fantasticamente legais elas são. Na verdade meu objetivo original era pedir sugestões, então talvez até houvesse um ponto em escrever o post, mas não sei mais. Se alguém quiser dar sugestões anyway, sinta-se à vontade.

Ao invés disso, eu vou falar de outra coisa: vou falar do fato de que os seres humanos se acostumam com as coisas e com o tempo passam a perceber qualquer situação em que se encontrem como algo dado e corriqueiro. Não, isso não é novidade nenhuma; você já se acostumou com essa idéia também. Mas às vezes o módulo de assumptions da minha cabeça tem um lapso de funcionamento e eu tenho um choque de realidade. Por exemplo, estava eu hoje à tarde debruçado sobre a mesa da sala da monitoria tentando dormir (já que normalmente não aparece ninguém lá para pedir ajuda e é difícil fazer qualquer coisa produtiva por lá, pois é difícil se concentrar com o barulho (mas não dormir, you see (embora eu não tenha conseguido dormir de fato (so far este blog contém 1083 pares de parênteses, por sinal, sem contar trechos de código entre tags PRE, mas isso não vem ao caso no momento)))). Sem sucesso em minha tentativa de dormir, levanto e resolvo sair para ir ao banheiro (leia-se: pretexto para caminhar um pouco). Em um estado semi-acordado, começo a andar pelo corredor para a rua. Nesse momento eu olho ao redor e penso: "WTF? I'm in the fucking Instituto de Informática? Na Universidade Federal do Rio Grande do Sul? Que viagem!" Não só estou no fucking Instituto de Informática, como também no final do curso. Especificamente, o curso termina em dois meses se eu me pilhar de terminar o TCC neste semestre (estrategicamente, entretanto, isso não tem vantagem nenhuma; é um semestre de RU a menos). How mad is that? E ainda por cima eu estou morando sozinho? Eu tenho uma casa só pra mim? Sou eu que pago as contas? Eu posso fazer whatever the hell I please da minha vida? Por que diabos ninguém tinha me contado isso antes?

E é isso, galera. A moral é que às vezes a gente precisa de uns tapas na cabeça para ela ir para o lugar certo. (Não digo "voltar para o lugar certo" porque às vezes ela nunca esteve no lugar certo.) Agora, se vocês me permitem, eu vou continuar lendo Hyperbole and a Half até dormir em cima do teclado, já que eu descobri há meia hora atrás que não vou ter aula amanhã.

5 comentários

Diru tuj, kiom estas la batalpovo de Kakarotto?

2013-04-28 23:52 -0300. Tags: esperanto, random, img

Estas pli ol ok mil!!!

Não, eu não tenho nada melhor para fazer (um TCC, por exemplo).

[P.S.: Tecnicamente era para ter um "ĝi" no começo da frase, eu suponho, mas não teria o mesmo impacto.]

3 comentários

Gravando áudio e eliminando ruído com o SoX

2013-04-26 19:52 -0300. Tags: comp, unix, audio, mundane

O SoX (SOund eXchange; pacotes sox e libsox-fmt-all no Debian) é uma biblioteca e um programa de linha de comando que permitem converter entre diversos formatos de arquivo de áudio, opcionalmente aplicando filtros. A sintaxe básica do comando sox é:

sox [opções-globais]
    [opções-de-formato] entrada1
    [[opções-de-formato] entrada2 ...]
    [opções-de-formato] saída
    [filtros ...]

Por exemplo, para gravar do microfone (usando ALSA) em um arquivo WAV:

sox -t alsa default -t wav blabla.wav

(Use Control-C para terminar a gravação. Tecnicamente o -t wav pode ser omitido, já que o sox é capaz de deduzir o formato do arquivo pela extensão.)

Um par de filtros particularmente interessante é o noiseprof/noisered, que permitem eliminar ou reduzir ruído constante de fundo. Isso é feito em duas etapas. Primeiro, executa-se o sox com o filtro noiseprof [profile.txt] sobre uma "amostra de silêncio", i.e., um trecho de áudio que consista apenas do ruído de fundo, de maneira a produzir um profile de ruído. Você pode capturar o "silêncio" do microfone ou de algum outro arquivo que consista apenas de silêncio (a opção --null pode ser usada no lugar do arquivo de saída, já que estamos interessados apenas no profile de ruído):

sox -t alsa default --null noiseprof profile.txt

sox algum-arquivo-que-consista-apenas-de-silêncio.wav --null noiseprof profile.txt

Alternativamente, você pode selecionar um trecho de um arquivo com o filtro trim início [duração] e usá-lo como fonte de silêncio:

# Seleciona o intervalo de de 1s até 2.5s. Aqui usamos '-t alsa default' como
# saída para podermos ouvir se o trecho selecionado de fato corresponde a "silêncio".

sox entrada.wav -t alsa default trim 1 1.5 noiseprof profile.txt

Se o nome do arquivo de profile for omitido, o sox escreve o profile na stdout.

Gerado o profile de ruído, podemos usar o filtro noisered [profile.txt [quantidade]] para remover o ruído do arquivo completo. quantidade é um número entre 0 e 1 indicando a quantidade de ruído que deve ser removida. Quanto maior o número, mais ruído será removido – e mais não-ruído também. Experimente com números pequenos (e.g., 0, 0.05, 0.1, etc.) primeiro.

sox entrada.wav saída.wav noisered profile.txt 0.05

Se você tem um microfone problemático, você pode querer guardar o arquivo de profile para usos futuros (assumindo que o padrão de ruído produzido seja sempre o mesmo).

Se o arquivo de entrada para o noisered não for especificado ou for -, o sox lê o profile da stdin. Assim, você pode combinar o profiling e a redução em um pipeline:

sox entrada.wav --null trim 0 1 noiseprof | sox entrada.wav saída.wav noisered - 0.05

Para mais informações, consulte a manpage do sox.

2 comentários

Underground revolutions

2013-04-20 02:48 -0300. Tags: about

O blog system foi completamente reescrito.

Se você não percebeu a diferença, a operação foi um sucesso.

(Caso contrário, deixe seu bug report nos comentários (assumindo que os comentários estejam funcionando).)

O código-fonte será publicado assim que algumas bagunças forem eliminadas (ou assim que alguém pedir, o que vier primeiro).

[Update (23/04/2013): Bagunças não foram eliminadas. O código está disponível.]

3 comentários

Main menu

Posts recentes

Tags

comp (72) unix (27) life (27) prog (26) random (22) lang (19) about (18) mundane (18) mind (14) web (13) rant (11) img (10) privacy (8) ramble (7) bash (7) esperanto (7) conlang (5) pldesign (5) home (4) misc (4) freedom (4) book (4) lisp (4) copyright (4) academia (3) kbd (3) film (3) music (3) politics (3) security (3) poem (2) editor (2) network (2) worldly (2) android (2) physics (2) c (2) php (2) cook (2) wrong (2) mestrado (2) pointless (1) audio (1) perl (1) shell (1) kindle (1)

Elsewhere

Quod vide


Copyright © 2012-2014 Vítor Bujés Ubatuba De Araújo
O conteúdo deste site, a menos que de outra forma especificado, pode ser utilizado livremente, com ou sem alterações, desde que seja mencionado o autor, preferivelmente com a URL do documento original.

Powered by Blognir.