Mandando o processador/fan se acalmar2012-05-12 01:03 -0300. Tags: comp, unix, mundaneAntes de destruir a máquina à minha frente e morrer de desgosto, compartilho com vocês o que eu descobri tentando fazer o cooler do PC parar de fazer barulho de espremedor de laranja. Algumas máquinas permitem controlar a velocidade do fan, por meio dos arquivos pwm1 e pwm1_enable nos diretórios/sys/class/hwmon/hwmon*. echo 1 >pwm1_enable habilita o controle do fan por software (ao invés de deixar o firmware trabalhar sozinho), e echo N >pwm1, onde N costuma ser um valor entre 0 e 255, controla a velocidade do fan. No EeePC, a configuração funcionava temporariamente até o firmware resolver que era melhor que o OS e ligar o fan de novo; para desligar o fan, era necessário então setá-lo de novo para a velocidade máxima e de volta para zero (ou o valor que se quisesse; o EeePC só tinha o modo "ligado" e "desligado" por software, entretanto). Os arquivos se chamavam fan1 e fan1_enable no kernel 2.6.159265358979 e anteriores. Outras máquinas não têm suporte ao PWM, mas possuem alguns controles interessantes nos diretórios /sys/class/thermal/cooling_device*. Cada um desses diretórios contém, entre outros, os arquivos: type, que diz o tipo do dispositivo em questão; cur_state, o estado atual do dispositivo; e max_state, o valor máximo que o estado pode assumir. Na máquina que estou usando agora (um Toshiba Satellite de uns cinco ou seis anos atrás), aparecem cinco dispositivos dos seguintes tipos: # grep '' /sys/class/thermal/cooling_device*/type /sys/class/thermal/cooling_device0/type:Processor /sys/class/thermal/cooling_device1/type:Processor /sys/class/thermal/cooling_device2/type:Fan /sys/class/thermal/cooling_device3/type:LCD /sys/class/thermal/cooling_device4/type:LCD Você pode dar echo N >/sys/class/thermal/cooling_devicex/cur_state para alterar o estado do dispositivo. Nesta máquina, o estado do fan fica sempre em 1 (ligado), e o valor não pode ser alterado. Os processadores/cores, porém, possuem um estado padrão 0 e um valor máximo 7; alterando esse valor, os processadores diminuem de velocidade, conseqüentemente aquecendo menos (e possivelmente gastando menos bateria, mas não cheguei a testar). Com um core desativado e o outro no estado máximo de economia, a máquina levou uns dez minutos para subir a temperatura (verificável em /sys/class/hwmon/hwmon*/temp*_input) de 50 a 80 graus (versus um minuto com dois cores no estado normal). Outra regulagem de freqüência que pode ser feita independentemente é através do cpufreq. cpufreq-info mostra a freqüência atual e a "política de freqüência" em vigor. As configurações podem ser alteradas com cpufreq-set: cpufreq-set -c 0 -u 1G seta a velocidade máxima do core 0 para 1GHz, por exemplo. (Cada modelo de processador aceita apenas certos valores de freqüência; este aqui, por exemplo, aceita 1GHz, 1.33GHz e 1.66GHz. O cpufreq seleciona o valor mais próximo que satisfaz o limite de velocidade especificado, ou retorna um erro.) Mais uma coisa: é possível desligar um core executando echo 0 >/sys/devices/system/cpu/cpuN/online (e reativá-lo trocando 0 por 1). Não testei o que acontece mandando desligar todos. Se sua máquina é capaz de ligar e desligar o fan, o que você pode fazer é criar um script para ligar o fan sempre que a temperatura passar de um certo limite (e.g., 80 graus) e desligá-lo quando ela voltar a uma temperatura baixa (e.g., 50 graus). Assim, você só vai ter que ouvir o fan de vez em quando. Na verdade, aparentemente existe um pacote chamado fancontrol que faz basicamente isso. Aqui, entretanto, o fan tem vontade própria e escolhe a velocidade que quer; para pará-lo, tive que impedi-lo de girar. A máquina esquenta igual, apenas mais devagar, com as configurações alteradas, o que não me resolve nada. Obviously it is Allah's will that I throw the Unix box out the window. I submit to the will of Allah. P.S.: Não, o Unix não tem nada que ver com o problema. P.P.S.: Aparentemente leitores prospectivos do blog esperam alguma coisa mais interessante da tag mundane. Ela apenas marca os posts sobre "arrumar PC" (i.e., o que sua família pensa que você faz quando diz que estuda Ciência da Computação) e resolução de problemas de mortal (por oposição aos posts sobre programação, por exemplo). Além disso, há uma tag mundane e uma worldly, completamente não-relacionadas, embora as palavras sejam sinônimos. There are no rules anywhere. The Goddess Prevails. Write yourself a Lisp right now!2012-05-10 00:20 -0300. Tags: comp, prog, lispQue tal escrever um interpretador em cem linhas de código? Let's ride! What?Um interpretador é um programa que lê um programa em uma dada linguagem e executa as ações descritas por esse programa. (Um compilador, por outro lado, lê um programa em uma linguagem e o traduz para outra linguagem, freqüentemente a linguagem de máquina de um processador. O processador, por sua vez, é um interpretador de linguagem de máquina implementado em hardware. But I digress.) Há duas linguagens envolvidas em um interpretador: a linguagem de implementação (em que o interpretador é escrito) e a linguagem implementada (que o interpretador interpretada). (Em um compilador há três linguagens envolvidas: a linguagem de implementação, a linguagem fonte e a linguagem alvo.) Essas linguagens podem coincidir (e.g., podemos escrever um interpretador de JavaScript em JavaScript), mas mesmo assim as entidades das duas linguagens freqüentemente são distintas (e.g., funções da linguagem implementada podem ser representadas por uma estrutura de dados da linguagem de implementação). Neste post, implementaremos um interpretador para uma linguagem Lisp-like em JavaScript. O objetivo primário é demonstrar o funcionamento geral de um interpretador. Assim, implementaremos apenas um "Lisp" bem básico, que em certos aspectos diverge dos Lisps típicos. Usaremos JavaScript para a implementação, em parte por ser uma linguagem bem conhecida, e em parte para podermos testar o interpretador no browser. O código desenvolvido neste post, prontamente modificável e experimentável, pode ser encontrado aqui. (Usarei JavaScript 1.8, o que significa que o código provavelmente só funciona no Firefox. Se houver demanda, traduzo para ECMAScript padrão*.) ParsingO primeiro passo na interpretação de um programa é a análise sintática, ou parsing. O parser lê o texto de um programa e produz uma estrutura de dados que reflete a estrutura sintática do programa. Por exemplo, ao se deparar com uma expressão do tipo 2*3+4*5 em uma linguagem de programação típica, o parser produzirá uma árvore do tipo:
+
/ \
/ \
* *
/ \ / \
2 3 4 5
Essa árvore reflete o agrupamento hierárquico dos elementos da expressão, segundo a sintaxe da linguagem em questão. Uma vantagem de se implementar um Lisp é que o texto de um programa em Lisp é uma representação direta e uniforme da árvore sintática do programa. A expressão acima, por exemplo, seria representada por (+ (* 2 3) (* 4 5)) em Lisp. Isso torna o parser bastante simples. De fato, ao contrário do que acontece com a maior parte das linguagens de programação, o significado de um programa em um Lisp tradicional não é definido em termos da sintaxe textual do programa. Ao invés disso, é definido um mapeamento da sintaxe textual para as estruturas em memória, e é a partir das estruturas que se define a semântica da linguagem. (+ 2 3) não "é" uma soma; (+ 2 3) é uma lista, que quando avaliada realizará uma soma. O resultado prático disso para nós neste momento é que é fácil de separar o parsing do resto do processo de interpretação em um Lisp; primeiro definimos os tipos de dados que podem ser encontrados no texto de um programa, e depois nos preocupamos com o que eles significam. Nossa linguagem terá os seguintes tipos de dados:
Um programa conterá uma seqüência de expressões (formas, em Lispês) independentes. Escreveremos nosso parser como uma função que recebe o texto do programa, faz o parsing de uma forma, e retorna a estrutura correspondente e o que sobrou do texto. Posteriormente, chamaremos o parser repetidamente com o que sobrou de cada passo de parsing, até consumir todo o programa. O parser procurará o primeiro caractere não-em-branco da entrada, e tomará ações distintas dependendo do que ele for. Se for um (, lemos itens até encontrarmos o ) correspondente, e os acumulamos em uma lista. Se for outro caractere, lemos a entrada até o próximo parêntese ou espaço; se o que encontramos consistir apenas de dígitos (possivelmente precedidos por um -), estamos diante de um número; caso contrário, encontramos um símbolo.
function read(text) {
text = text.trimLeft(); // Pula espaços iniciais.
if (text == "")
throw "EOF";
else if (text[0] == "(") {
var list = [];
text = text.substring(1); // Pula o '('.
while ([item, text] = read(text), item!=")")
text.push(item);
return [list, text];
}
else if (text[0] == ")")
return [")", text];
else {
match = text.match(/^[^\s()]+/); // Lê tudo até o primeiro parêntese ou espaço.
if (match[0].match(/^-?\d+$/))
return [Number(match[0]), text.substring(match[0].length)];
else
return [match[0], text.substring(match[0].length)];
}
}
Agora, se chamarmos read("1 2 3"), teremos como resultado [1, " 2 3"], e se chamarmos read("(+ 2 3)"), teremos [["+",2,3], ""]. Podemos nos dar por satisfeitos com o parser por ora. SemânticaFeito o parsing, podemos fazer a interpretação propriamente dita, isto é, executar as operações indicadas pelo programa. Nossa linguagem é baseada em expressões; o significado de um programa é especificado em termos de como é avaliada (evaluated) cada expressão do programa, i.e., qual é o valor de cada expressão, e possíveis efeitos colaterais de avaliar uma expressão. (Outras linguagens possuem tanto expressões quanto statements, comandos que não possuem valor, mas apenas efeito colateral.) Números são avaliados para si mesmos; o valor da expressão 2 é o próprio 2. Símbolos são interpretados como referências a variáveis. Em qualquer ponto da avaliação, há um ambiente (environment), um conjunto de associações de nomes de variáveis aos seus respectivos valores. O valor de um símbolo é o valor da variável nomeada pelo símbolo no ambiente em que ele é avaliado. Se a variável não existir, um erro de execução ocorre. É importante notar que trechos de programa diferentes podem ser avaliados em ambientes diferentes. É possível criar variáveis locais, visíveis em apenas um trecho do programa. Por exemplo, os parâmetros de uma função são visíveis apenas pelas expressões contidas na função. Cada chamada à função cria um novo ambiente, associando os parâmetros da função aos valores usados naquela chamada. Além dos parâmetros, as expressões da função também verão as variáveis definidas fora da função (desde que elas não tenham os mesmos nomes dos parâmetros). Como se pode ver, ambientes são hierárquicos: o valor de uma expressão é procurado primeiro no ambiente mais recentemente criado (e.g., os parâmetros da função); se a variável não for encontrada aí, ela será procurada em ambientes mais externos, até chegar no ambiente global. Representaremos ambientes no nosso interpretador por objetos simples (associações nome/valor) em JavaScript; ligaremos um ambiente ao ambiente imediatamente mais externo a ele através de um campo _parent no objeto. E.g., {x:1, y:2, _parent: {a:3, b:4}} representa um ambiente com as variáveis x e y, subordinado a um ambiente com as variáveis a e b. Para encontrar uma variável em um ambiente, usaremos a função lookup:
function lookup(name, env) {
if (env) {
if (env[name] !== undefined)
// Se o nome está presente no ambiente atual, retornamo-lo.
return env[name];
else
// Caso contrário, procuramos no ambiente pai.
return lookup(name, env._parent);
}
else {
// Se mandamos procurar no ambiente pai, e o ambiente pai não existe, paramos aqui.
throw "Unbound variable " + name;
}
}
Com isso, podemos começar a escrever o nosso avaliador de expressões. Ele recebe uma expressão para avaliar e o ambiente onde a expressão será avaliada:
function eval(code, env) {
if (typeof(code) == "number")
return code;
else if (typeof(code) == "string")
return lookup(code, env);
...
Falta definirmos como avaliaremos listas. Uma lista é avaliada da seguinte maneira: se o primeiro elemento da lista for o nome de um operador especial (como if), a avaliação segue segundo as regras do operador; caso contrário, o primeiro elemento é tomado como uma função a ser chamada usando os outros elementos como argumentos. Assim, (f x y) é interpretado como a chamada da função f com os argumentos x e y. Finalmente, se a lista for vazia, o resultado é a própria lista vazia. Nossa linguagem tem quatro operadores especiais. Um deles, lambda, é usado para criar funções. (lambda (x y) (+ x y)) cria uma função que recebe dois argumentos, nomeados x e y, e retorna a soma dos dois. Representaremos as funções do nosso Lisp por um objeto em JavaScript contendo a lista de parâmetros, o corpo da função, e o ambiente em que a função foi criada. (A chamada da função pode ocorrer posteriormente em um ambiente completamente diferente do ambiente em que ela foi criada; queremos, entretanto, que o corpo da função seja avaliado no ambiente original da definição. Caso contrário, se alguém criar uma variável chamada + e chamar a função que acabamos de definir, ela não mais necessariamente somará dois números.)
else if (code.length == 0)
return []; // Se for a lista vazia, retorna vazio.
else {
switch (code[0]) {
case "lambda":
return { type: "lambda", args: code[1], body: code[2], env: env };
lambda cria uma função, mas não lhe dá um nome. O segundo operador especial da nossa linguagem é o define: (define nome valor) cria uma variável chamada nome no ambiente atual, com o valor especificado. Para avaliar essa expressão, avaliamos o valor, e acrescentamos a variável ao ambiente:
case "define":
return env[code[1]] = eval(code[2], env);
O terceiro operador é o if: (if test then else) avalia test; se test for diferente da lista vazia, avalia-se then; senão, avalia-se else. O resultado da expressão inteira é o valor da expressão escolhida segundo o teste.
case "if":
if (eval(code[1], env).length !== 0)
return eval(code[2], env);
else
return eval(code[3], env);
O último operador é o quote: (quote x) retorna x intacto, sem avaliar. O quote serve para quando queremos tratar um símbolo ou lista como um dado, não como um trecho de código a ser avaliado; (quote (+ 2 3)) simplesmente retorna a lista (+ 2 3) intacta.
case "quote":
return code[1];
Por último, o caso em que o primeiro elemento da lista não é um operador especial: devemos avaliar todos os elementos da lista, e chamar a função resultante da avaliação do primeiro:
default:
var evals = code.map(function(x) eval(x, env));
var fun = evals[0];
var args = evals.slice(1);
Na verdade teremos dois tipos de função no nosso interpretador: funções definidas pelo usuário, que possuem um corpo escrito em Lisp e que deveremos avaliar, e funções embutidas no interpretador (como +), que serão definidas diretamente em JavaScript. Representaremos as funções embutidas por funções do JavaScript; para avaliar uma chamada a uma função embutida, basta chamar a função com o resultado da avaliação dos argumentos:
if (typeof(fun) == "function")
return fun.apply(null, args);
Se se trata de uma função definida pelo usuário, devemos avaliar o corpo da função em um ambiente associando os parâmetros da função com os argumentos com que foi chamada. Além disso, a função deve enxergar as variáveis que existiam quando ela foi definida; logo, o ambiente de definição deve ser linkado como ambiente pai.
else if (typeof(fun) == "object" && fun.type == "lambda") {
var newenv = { _parent: fun.env };
for (var i in fun.args)
newenv[fun.args[i]] = args[i];
return eval(fun.body, newenv);
}
Por último, se o primeiro item não for uma função, reportamos um erro:
else
throw "Not a function: " + fun;
}
}
}
A parte importante do interpretador está pronta. Falta é definir um ambiente padrão, com algumas funções básicas. Eis algumas funções aritméticas:
var stdenv = {
"+": function(x,y) x+y,
"-": function(x,y) x-y,
"*": function(x,y) x*y,
"/": function(x,y) x/y,
Aqui as definições são bastante simples, pois os números do nosso Lisp são representados por números do JavaScript. Se não fosse esse o caso, teríamos um passo de conversão dos argumentos para números do JavaScript, e do resultado de volta para números do Lisp. As funções de comparação são menos diretas: devemos converter os booleanos do JavaScript (true e false) para os do Lisp (whatever e ()). (Qualquer coisa diferente de () é verdadeira, segundo o if; convencionalmente, se usa o símbolo t para representar "verdadeiro".)
"=": function(x,y) x==y? "t" : [],
"<": function(x,y) x<y? "t" : [],
...
Outras funções importantes em um Lisp são as funções de manipulação de listas: (first ls) retorna o primeiro elemento de uma lista, (rest ls) retorna a lista sem o primeiro elemento, e (cons x ls) cria uma lista nova adicionando x à lista original.
"first": function(ls): return ls[0],
"rest" : function(ls): return ls.slice(1),
"cons" : function(x,ls) : return [x].concat(ls),
Além disso, temos os testes null?, que testa se um objeto é a lista vazia, e cons?, que testa se um objeto é uma lista não-vazia.
"null?": function(x) typeof(x)=="object" && x.length==0? "t" : [],
"cons?": function(x) typeof(x)=="object" && x.length>0? "t" : [],
}
E está pronto! Já podemos chamar nosso interpretador:
eval(read("(define fac (lambda (n) (if (= n 0) 1 (* n (fac (- n 1))))))")[0], stdenv);
alert(eval(read("(fac 5)")[0], stdenv));
Não criamos uma função para ler todas as expressões de um programa e chamar eval com cada uma, nem criamos meios de chamar o interpretador sem escrevermos a chamada no fonte do interpretador ou em um prompt, mas esses são detalhes menores. Você pode usar a interface mencionada no começo do post para experimentar o interpretador. * "1995 - Brendan Eich reads up on every mistake ever made in designing a programming language, invents a few more, and creates LiveScript. Later, in an effort to cash in on the popularity of Java the language is renamed JavaScript. Later still, in an effort to cash in on the popularity of skin diseases the language is renamed ECMAScript." (A Brief, Incomplete, and Mostly Wrong History of Programming Languages) ** O nome do post foi inspirado no Write Yourself a Scheme in 48 Hours, um tutorial de Haskell. The art of the prompt string2012-05-06 03:49 -0300. Tags: comp, unixOfereço este post com conteúdo útil como sacrifício aos leitores, de modo a ter crédito para escrever posts reclamando da vida mais tarde. O prompt padrão do bash na maior parte das distribuições de GNU/Linux costuma ser algo do tipo username@hostname diretório$ . Esse prompt pode ser customizado alterando-se o valor das variáveis PS1 ("prompt string 1") e companhia: vitor@eukleides ~$ PS1='oi? ' oi? pwd /home/vitor oi? Você pode experimentar prompts diferentes alterando essa variável até achar um que seja do seu agrado, e então alterar um dos arquivos de configuração do bash (e.g., ~/.bashrc) para setar a variável para o valor desejado na inicialização do shell. Backslash sequencesComo você pode imaginar, o conteúdo de PS1 não precisa ser texto estático. O bash substitui certas seqüências de \ seguido de um caractere (e.g., \u) na definição do prompt por valores específicos (e.g., o nome do usuário). O prompt padrão apresentado acima pode ser obtido pela string \u@\h \W\$ :
Uma lista completa das seqüências expandidas pelo bash no prompt pode ser obtida procurando por PROMPTING no manual (man bash). Outras seqüências úteis incluem \w (caminho completo do diretório atual) e \! (número do comando atual no histórico; útil para quem gosta de usar os comandos de recuperação do histórico, e.g., !42 para reexecutar o comando de número 42). Escape sequencesUma das maravilhas do terminal (que talvez seja digna de um post a respeito no futuro) são as escape sequences: seqüências de caracteres especiais que podem produzir toda sorte de firula, desde cores e outros efeitos de texto até alterações no título da janela de terminal. Por exemplo, a seqüência ESC [ 3 número m, onde ESC é o caractere ASCII número 27, seleciona a cor de texto com o número especificado (que varia de 0 a 7); a seqüência ESC [ 0 m retorna as cores e outros atributos para seus valores padrão. O caractere ESC pode ser representado no prompt pela seqüência \e. Assim: PS1='\e[34m\u@\h \W\$ \e[0m' lhe dará um prompt idêntico ao padrão, mas em azul. Existe uma manpage (man console_codes) com uma lista de seqüências ESC suportadas por diversos terminais. Só tem um problema: o bash usa o tamanho da string de prompt expandida para calcular o tamanho que o prompt ocupa na tela; ele precisa dessa informação para determinar onde ocorre a quebra de linha, caso o comando digitado possua mais de uma linha, e para saber onde posicionar o cursor caso você tecle Home, entre outras coisas. O problema é que os caracteres de uma seqüência ESC não ocupam espaço na tela, mas aumentam o tamanho da prompt string. Para o bash poder calcular corretamente o tamanho do prompt, é necessário demarcar os trechos de seqüências ESC no prompt entre \[ e \]: PS1='\[\e[34m\]u@\h \W\$ \[\e[0m\]' Provavelmente a coisa mais útil que se costuma fazer com seqüências ESC no prompt é setar o título da janela de terminal para conter o diretório atual: PS1='\[\e]0;\w\a\]\u@\h \W\$ ' ESC 0 ; título BEL é a seqüência que muda o título da janela do xterm e outros terminais gráficos. (BEL, representado no prompt por \a, é o caractere ASCII número 7; em situações normais, ele é o caractere que faz o terminal emitir um beep.) Se você nem sempre acessa o terminal via interface gráfica, pode ser uma boa idéia testar se o terminal atual é gráfico antes de adicionar seqüências ESC no prompt:
case "$TERM" in
xterm*|gnome*|konsole*) PS1='\[\e]0;\w\a\]\u@\h \W\$ ' ;;
*) PS1='\u@\h \W\$ ' ;;
esac
Ou, alternativamente, para evitar duplicação:
# Primeiro adiciona a seqüência ESC.
case "$TERM" in
xterm*|gnome*|konsole*) PS1='\[\e]0;\w\a\]' ;;
*) PS1='' ;;
esac
# Depois, o resto do prompt (igual em ambos os casos).
PS1+='\u@\h \W\$ '
VariáveisAlém de backslash sequences, o bash substitui variáveis presentes na prompt string. Por exemplo, PS1='$USER@$HOSTNAME $PWD\$ ' tem mais ou menos o mesmo efeito que PS1='\u@\h \w\$ '. Ao se usar variáveis no prompt, é importante usar aspas simples em volta do valor que está sendo atribuído a PS1:
O bash também expande comandos (usando a sintaxe $(comando) ou `comando`) presentes na string de prompt. Novamente, é importante cuidar para usar aspas simples caso se queira que o comando execute toda vez que o prompt é impresso, e não apenas uma vez durante a definição do prompt. De modo geral, executar um comando (criar um processo) cada vez que o prompt é impresso não me parece uma boa idéia; normalmente, executar o comando uma vez só resolve a maior parte dos problemas e é menos custoso computacionalmente. (Se bem que hoje em dia criar um processo provavelmente não vai fazer uma diferença palpável na execução do shell ou do sistema; eu recomendaria não fazer isso com o prompt do root, entretanto. A gente nunca sabe quando vai ter que matar uma fork bomb ou um processo mal-comportado no sistema.) Uma utilidade de usar variáveis e/ou comandos é incluir nome da tty atual no prompt. Isso é particularmente útil quando se está trabalhando no modo texto, pois pelo nome da tty sabe-se o número do console virtual (e lembrando o número pode-se rapidamente voltar para o console em questão teclando Alt-Fn).
tty="$(tty)" # Recupera o nome completo da tty (e.g., /dev/tty1)
if [[ $tty == */pts/* ]]; then
ttybase="pts${tty##*/}" # Transforma /dev/pts/0 em pts0
else
ttybase="${tty##*/}" # Transforma /dev/tty1 em tty1
fi
PS1='\u@$ttybase \W\$ ' # Usa o nome da tty ao invés do hostname.
Ganhamos o nome da tty, mas perdemos o hostname. Uma possibilidade é testar se a conexão atual é via ssh, e usar o hostname nesse caso:
tty="$(tty)"
if [[ $SSH_CONNECTION ]]; then
ttybase="$HOSTNAME"
elif [[ $tty == */pts/* ]]; then
ttybase="pts${tty##*/}"
else
ttybase="${tty##*/}"
fi
PS1='\u@$ttybase \W\$ '
Outra informação útil de se incluir no prompt (uma que eu já não consigo viver sem, a ponto de alterar o prompt de outras máquinas quando vou usá-las por mais do que alguns minutos) é o exit status do último comando. O exit status é um valor ente 0 e 255 que cada processo retorna ao terminar. (É por isso que a main() retorna int, e não void, em C.) Por convenção, um programa retorna 0 se foi bem-sucedido, e um valor diferente de zero caso tenha ocorrido um erro (valores diferentes podem ser usados para erros diferentes). O exit status do último comando vive na pseudo-variável $?: PS1='\u@$ttybase \W($?)\$ ' A presença do exit status no prompt é útil por mil razões. Primeiro, se você costuma escrever shell scripts, freqüentemente deseja saber que valor um comando retorna em certa situação. Além disso, o exit status presente no prompt dá um feedback imediato quanto ao sucesso ou falha do último comando, sem ter que ler toda a saída do comando. Por exemplo, ao copiar um diretório com muitos arquivos com cp -v, pode ser que uma mensagem de erro passe desapercebida no meio da saída normal do cp; com o exit status no prompt, o erro é imediatamente visível. Com o tempo, verificar o exit status é um ato inconsciente; para mim, hoje em dia, ele é um feedback tão importante quanto a saída do comando. Finalmente, existem alguns programas cretinos que não imprimem mensagens de erro, apenas retornam 1 para indicar que falharam. (Lamentavelmente, com o exit status no prompt, você possivelmente se sentirá inclinado a escrever programas que fazem o mesmo. Eu não tenho o direito de atirar pedras.) The prompt commandA última maravilha promptística que tenho a apresentar é a variável PROMPT_COMMAND: se essa variável for setada, seu valor é interpretado como um comando a ser executado antes de imprimir o prompt. Você pode usá-la para chamar alguma função que seta uma variável usada na string do prompt. Eis uma possibilidade: nos doces tempos do bash 1.14, o comportamento do \W era diferente: não havia abreviação do diretório home para ~; além disso, se o diretório estivesse diretamente abaixo do raiz (e.g., /mnt), o bash imprimia a / no início do nome, e não apenas mnt. O comportamento mudou no bash 2, e me foi a mi mui mal. Felizmente, é possível corrigir esse problema usando o PROMPT_COMMAND e uma variável no prompt:
redpwd() {
case "$PWD" in
/*/*) REDPWD="${PWD##*/}" ;;
*) REDPWD="$PWD" ;;
esac
}
PROMPT_COMMAND="redpwd"
PS1='\u@$ttybase $REDPWD($?)\$ '
Pode-se adaptar a redpwd, com uma pequena dose de falcatrua, para mostrar os últimos dois itens do diretório atual, ao invés do caminho completo ou do último item:
redpwd() {
local prefix
case "$PWD" in
/*/*/*)
# Se PWD = /mnt/foo/bar/baz/quux:
prefix="${PWD%/*/*}" # prefix = /mnt/foo/bar
REDPWD="${PWD#"$prefix"/}" # REDPWD = baz/quux
;;
*)
REDPWD="$PWD"
;;
esac
}
Pode-se obter a temperatura da CPU, ou a carga da bateria da máquina, a partir dos arquivos em /sys. Pode-se baixar periodicamente informação climática de algum site e adicionar a temperatura ao prompt, mudando a cor dependendo de se o tempo está ensolarado ou nublado ou chuvoso. As possibilidades são infinitas. A story of failure2012-04-29 19:27 -0300. Tags: life, pointlessAcordo, tomo café. Em um estado semi-consciente, a voz que me lembra dos meus afazeres quase tarde demais me informa: "Os créditos do 3G vencem hoje." Olho o relógio: são seis da tarde. É domingo. Terei que ir ao mercado a quinze minutos de casa. Está chovendo. Vou aproveitar para comprar molho, penso eu. E massa. E chocolate, talvez. Coloco uma nota de dinheiro no bolso, visto um casaco, pego um guarda-chuva, saio de casa. Caminho quinze minutos pelas rochosas colinas de Svartálfheimr. Chego. Me dirijo à prateleira com sachês de molho: não tem o que eu quero. Contemplo a prateleira de uma distância maior, em toda sua glória, por mais alguns instantes. Realmente não tem. Não vou levar molho, penso eu. Não vou levar nada. Saio da área das prateleiras, e me dirijo à lojinha da Tim que fica em uma galeria escondida dentro do mercado. Está fechada. Me dirijo ao caixa automático. Tem gente na fila. Enquanto isso eu me pergunto se tem como fazer recarga pelo caixa, e se ela vai ser efetuada no mesmo dia. Concluo que não. Abandono a fila, me lembro da existência de uma lojinha de porcarias na parte da frente do mercado que faz recargas. Fechada. Vou embora. Saio na rua. Escuro, chuva. Ainda estou semi-dormindo. Saio andando no caminho para casa, olhando em volta para ver se encontro algum lugar aberto que faça recarga. Atravesso a rua. Olho para trás. Há uma locadora aberta. Não há qualquer indício de que façam recarga ali, mas também não há indício de que não façam. Contemplo a locadora do outro lado da rua. "Vocês fazem recarga de Tim?", medito sobre o diálogo. Procedimento padrão. "Qual o número?", me responde o atendente idealizado. Não sei o número de cabeça. Tenho ele anotado no celular. Não trouxe o celular. Contemplo a locadora. Contemplo o escuro. Melhor passar o resto da noite debaixo das cobertas. Hel2012-04-29 00:21 -0300. Tags: musicMais uma bandinha que ninguém conhece. Hel é uma banda sueca de viking rock, notável pelos vocais femininos. Hel – Eldsjäl (Fire soul)
Tradução roubada dos comentários do YouTube. (Agradeça-me; andar por lá é uma experiência lamentável.) Mecânica quântica2012-04-28 00:15 -0300. Tags: physicsDesde o século 17 os físicos estavam se degladiando sobre a questão de se a luz é feita de partículas ou de ondas. Um experimento interessante feito no processo consiste em colocar uma barreira com duas fendas entre uma fonte de luz e uma superfície. O resultado não é meramente duas regiões iluminadas na superfície: o que se observa é um padrão de regiões iluminadas e escuras. A explicação é que as ondas de luz provenientes de cada uma das fendas interferem-se mutuamente, causando um padrão de reforço e cancelamento. Embora esse resultado aponte evidentemente um comportamento ondulatório, alguns outros fenômenos, como o efeito fotoelétrico, podiam ser mais bem explicados como a interação de partículas de luz com a matéria. No fim das contas, a galerinha concluiu que a luz é feita de pacotes de energia discretos (chamados quanta (singular quantum)), mas possui comportamento ondulatório. A partir daí as coisas começam a ficar preocupantes. Em primeiro lugar (mas não necessariamente nessa ordem), descobriu-se que um experimento similar realizado com elétrons (um bicho que todos estavam de acordo que era uma partícula) também resulta em um padrão de interferência. Outros experimentos foram feitos com outras partículas, com resultados similares. Isto é, toda a matéria possui comportamento ondulatório. Ainda mais preocupantemente, um experimento similar em que é lançado um fóton (partícula de luz) de cada vez, de tal maneira que há probabilidade igual de cada fóton passar por cada uma das fendas, também produz um padrão de inferferência. Cada fóton individual interfere consigo mesmo. Mas como diabos, se cada fóton passa por apenas uma fenda? Ou será que não passa? Para tentar resolver o mistério, realizou-se uma variação do experimento, com um detector próximo a cada fenda, de modo a determinar por qual fenda cada elétron individual estava passando. O resultado do experimento foi o seguinte: um, cada fóton individual de fato passava por uma única fenda; dois, na versão modificada do experimento, os fótons não formam um padrão de interferência: os fótons formam o padrão que se esperaria de partículas sólidas, atingindo duas regiões da superfície. E é assim que funciona a porcaria: o fóton se comporta como uma onda enquanto ninguém está olhando, mas se alguém for ver por que fenda ele passa, ele decide passar por uma fenda ou outra, como uma partícula bem comportada. A coisa fica um pouco pior, na verdade. É possível realizar o teste da fenda de maneira reversível. Ao invés de testar diretamente a passagem do fóton por cada uma das fendas, manipula-se o fóton de tal maneira que o spin do fóton é diferente dependendo de por qual fenda ele passou, e detecta-se o spin quando o fóton atinge a superfície. Se isso for feito, o fóton também se comporta como partícula, sem padrão de interferência; mas se no meio do caminho o fóton for manipulado de novo, de modo que o spin volte a ser o mesmo independentemente de por qual fenda o fóton passou, o padrão de interferência ressurge. O fóton, troll master, se comporta como se passasse por ambas as fendas, até que tenhamos como descobrir por qual ele passou, momento em que ele decide passar por apenas uma das fendas; mas se nós só ameaçarmos descobrir a informação e a atirarmos pela janela, ele novamente se comporta como se tivesse passado por ambas, mesmo depois de já ter teoricamente decidido passar por apenas uma fenda. A teoria matemática da mecânica quântica prediz todos esses fatos; os experimentos foram feitos para confirmar se as predições da teoria estavam corretas, e estão. A teoria associa a cada partícula uma wavefunction, uma descrição ondulatória de seu comportamento. Não há consenso quanto a o que a wavefunction é fisicamente; o fato é que o quadrado da amplitude da wavefunction em um ponto do espaço representa a probabilidade de a partícula ser observada no ponto em questão. Fica a questão de por que sempre observamos uma partícula em um único ponto, se ela é de fato uma onda, e o porquê do comportamento probabilístico. A formulação matemática não informa nada disso; ela prediz o comportamento das partículas, e pronto. As equações não estão preocupadas com a interpretação física dos números. Mas interpretações não faltam. A mais antiga, e certa vez mais aceita, e possivelmente ainda mais aceita, é a interpretação de Copenhagen. Segundo ela, a wavefunction é apenas uma descrição matemática, sem existência física, que descreve as probabilidades de se encontrar uma partícula em um dado estado. Quando a partícula é observada (e.g., detectada por um equipamento), ela imediatamete assume um estado único: a wavefunction colapsa, apresentando um pico no ponto onde a partícula é encontrada, e uma amplitude de zero elsewhere. Uma entidade se comporta sempre como uma partícula ou como uma onda, mas nunca como ambos. Embora essa descrição se encaixe com os resultados dos experimentos, ninguém nunca ficou muito satisfeito com o colapso da wavefunction dependente de observação. Schrödinger (o camarada que descobriu a equação que descreve o comportamento da wavefunction ao longo do tempo) criticou a dependência de um observador, sugerindo o experimento mental conhecido como o gato de Schrödinger. A idéia é basicamente a seguinte: coloca-se um gato dentro de uma câmara isolada com um equipamento que mede um determinado evento quântico que tem igual probabilidade de ocorrer ou não ao longo de uma hora. Se o equipamento detectar o evento, ele parte um frasco de ácido cianídrico dentro da câmara, matando o gato. (Tal é a perversão das mentes que avançam a ciência.) A interpretação de Copenhagen, Schrödinger alega, leva à conclusão de que enquanto a caixa não for aberta (i.e., enquanto o ambiente não for observado), o gato estará em um estado sobreposto, vivo e morto ao mesmo tempo.
Se a interpretação de Copenhagen de fato diz isso é um ponto controverso. O que o experimento evidencia é que a noção de "observação" na interpretação de Copenhagen é vaga. Segundo alguns, a medição do evento quântico feita pelo equipamento já é suficiente para causar o colapso da wavefunction. (Além disso o gato está observando o ambiente, mas esse detalhe costuma ser ignorado. Ninguém se importa muito com o gato nessa história.) A segunda interpretação mais aceita é a many-worlds interpretation. A many-worlds afirma que a wavefunction é real: ela é uma entidade física, não apenas um dispositivo matemático. De fato, segundo a many-worlds, a wavefunction de uma partícula é a partícula; não há nada na partícula que não seja descrito por sua wavefunction. A wavefunction é totalmente determinística (regida pela equação de Schrödinger), e não há colapso. Assim sendo, como se explica a observação de que partículas sempre se encontram em apenas um ponto quando observadas? A idéia é a seguinte: no momento em que a partícula interage com o observador (e.g., o equipamento de medição), o estado de ambos se torna entrelaçado. Não faz sentido falar sobre o estado isolado de cada um dos sistemas (partícula e observador); só se pode falar de o estado de um relativo ao do outro: se a partícula tem um estado x, então o observador tem um estado y. Mas como todos os estados possíveis da partícula estão presentes na wavefunction, e a wavefunction é real, então todos os estados da partícula são reais em um dado momento. O que acontece é que quando observamos a partícula, o nosso estado fica entrelaçado com o da partícula, de modo que para cada possível estado da partícula (e.g., passar pela fenda da esquerda ou da direita), há um estado nosso correspondente (ter observado a passagem da partícula pela fenda da esquerda ou da direita). A implicação prática é que é como se para cada possibilidade existisse um universo paralelo, e em cada um deles houvesse um observador observando a partícula em um de seus possíveis estados. Trata-se, entretanto, do mesmo universo, com a wavefunction do observador entrelaçada com a da partícula, realizando todas as possibilidades do sistema. Os "mundos" só podem interferir um com o outro se o estado de todas as partículas for idêntico em ambos (i.e., quando deixam de ser múltiplos mundos). Em um sistema de escala sub-atômica isolado é possível que os estados divergentes da partícula convirjam no futuro (que é o que ocorre no experimento das fendas). Para sistemas maiores, a possibilidade de interferência é mínima, e diminui cada vez mais com o aumento da entropia (que a princípio é irreversível, embora não haja dados suficientes para uma resposta significativa). Segundo a many-worlds, portanto, o gato está vivo e morto ao mesmo tempo, mas em "branches" diferentes da wavefunction; os branches estão entrelaçados com o outcome do evento quântico, e portanto o estado em que o evento ocorre está correlacionado com o estado do gato morto, e o estado em que o evento não ocorre com o gato vivo. Um observador externo sempre verá esses eventos correlacionados. Mas isso ocorre porque o estado do observador (ver o gato morto ou ver o gato vivo) também está entrelaçado com os estados da partícula e do gato. Existem outras interpretações, mas essas são as que têm mais mind share entre os físicos. Não sou um grande conhecedor do assunto, e este post pode conter erros (ele pode estar correto em outro branch da wavefunction, se isso lhe serve de consolo). Minhas referências são a fonte de todo o conhecimento, um FAQ sobre a many-worlds (que a explica muito melhor do que qualquer outra coisa que eu tenha lido sobre ela), e um livrinho supimpa chamado The Fabric of the Cosmos. Sermon on Ethics and Love2012-04-24 01:33 -0300. Tags: random
A Terra não era plana na Idade Média2012-04-21 03:13 -0300. Tags: misc
Today I learned. 3G dos infernus2012-04-19 22:08 -0300. Tags: about, comp, mundaneParece que ficarei sem sinais de fumaça por tempo indeterminado... Edit: It's back, às duas da madruga. 3G em Viamão é a maravilha das brilhantes maravilhas. Caso esteja pensando em adquirir um, eis o que posso lhe informar: Atualmente estou usando um 3G da Tim. O preço provavelmente é o melhor que pode ser obtido: com o plano Infinity, se paga 50 centavos por dia (ou seja, 15 reais por mês, na média), e o plano é pré-pago. A banda mensal é ilimitada; supostamente, quando o tráfego mensal passa de 350MB, a velocidade é "reduzida" para 64kbps; na prática, a velocidade nunca passa de 6kB/s (= 48kbps) em qualquer período do mês. A latência normal (medida com ping) é por volta de 1 segundo (vs. os 50ms ou menos do ADSL), mas freqüentemente chega a 4 segundos ou mais. Costumava não cair, mas ultimamente tem caído (e por vezes ficado sem serviço por algumas horas) com uma freqüência menos que agradável. Hoje, quando houve a queda de serviço, experimentei conectar usando a linha Oi do meu celular. Não tenho plano de dados nem nada que o valha nessa linha, portanto a conexão saiu por um real o minuto. Creio que não exista plano 3G pré-pago pela Oi, e não sei quais são os planos e preços atuais, mas quando procurei era mais do que 15 reais por mês. A velocidade da conexão é mais ou menos a mesma (6kB/s), mas a latência é menor (cerca de 300ms). Para acessar meu diretório home na Inf via sshfs, a latência menor fez uma diferença gritante. Porém, ter um contrato fixo e pagar mais só para ter uma latência menor não está valendo. Não sei do sinal das outras operadoras; sei que são caras. A Vivo não quis me vender o serviço porque eu uso GNU/Linux. E isso que eu insisti (mas não muito; me tapei de desgosto em alguns instantes). By the way, quando fui para Torres no começo do ano, o 3G da Tim foi simplesmente inutilizável. (Consegui acessar o webmail da Inf, depois de cerca de 15 minutos, setando o timeout do Firefox para 5 minutos (opções network.websocket.timeout.* no about:config).) By the way[2], o modem que a Tim fornece (ONDA MSA 190) funciona no Linux, mas requer uma treta. Descobri como fazer num fórum do Viva o Linux, mas é mais fácil explicar do que encontrar a página em questão (que além do mais não era particularmente organizada). Assumo um sistema Debian ou derivado (leia-se Ubuntu):
Hora de dormir, e esperar que o futuro seja melhor amanhã. Checked exceptions2012-04-18 01:03 -0300. Tags: comp, prog, rantPassei a vida mantendo uma distância saudável do public static void fucking Java, com exceções esporádicas que ajudaram a reforçar o sentimento. Esse semestre, entretanto, estou fazendo a rica cadeira de Programação Paralela e Distribuída, cujos exercícios de laboratório devem ser todos feitos em Java. Nesses momentos de diversão incomensurável descobri mais uma das maravilhas dessa linguagem: o conceito de checked exceptions. A idéia é genial: na declaração de um método, é possível adicionar uma cláusula throws exceção, que informa ao compilador que o método pode lançar a exceção em questão, e o programador é obrigado a tratá-la quando chamar o método, ou a declarar que o seu método também pode lançar a exceção. O resultado prático, obviamente, é que enquanto se está testando o programa acaba-se adicionando tratadores de exceção do tipo: try { do_whatever(); } catch (Exception e) {}
Até que de fato queremos debugar um problema com do_whatever(), e queremos o bom e velho stack trace que acompanha a exceção, mas que está sendo engolido pelo catch vazio. Nesse momento, mudamos o tratador para:
try {
do_whatever();
} catch (Exception e) {
System.out.println(e);
e.printStackTrace();
}
Que é, bem, o tratador de exceções padrão. Não é ótimo ter que escrever você mesmo o tratador de exceções padrão da linguagem? Além de, de certa forma, matar o propósito das exceções (isolar o tratamento de erros do resto do código), o que os criadores da linguagem aparentemente não conceberam é que enquanto eu estou escrevendo o programa, ele não está pronto. Eu quero poder ir testando o programa à medida em que vou escrevendo, e não quero entupir o código com tratadores de exceção enquanto estou fazendo o código funcionar. De fato, o que eu quero normalmente é não tratar uma exceção, para obter um stack trace. Talvez eu sequer tenha decidido ainda qual a melhor maneira de tratar o erro. (O quê?! Você não tinha um projeto detalhado do programa antes de começar a escrevê-lo? Blasfêmia! Vá vestir sua gravata e volte para os diagramas UML.) Lisp foi provavelmente a primeira linguagem a incorporar a idéia de que é útil executar programas incompletos, e o Common Lisp provavelmente ainda é a linguagem que faz isso com mais competência. Em (Common) Lisp, chamadas a funções inexistentes, chamadas com número errado de argumentos, tipos diferentes dos esperados, etc., geram erros de execução. Um bom compilador também gera warnings em tempo de compilação ao encontrar esses problemas, mas não impede a compilação e execução do programa. O programador pode então decidir corrigir os erros imediatamente, quando possível, ou executar o programa incompleto para fins de teste. Erros de execução em Common Lisp não encerram o programa, mas abrem o debugger, de onde se pode averiguar e alterar o estado do programa, e erros geralmente são continuáveis, i.e., é possível prosseguir com a execução de um programa após a ocorrência de um erro de execução. Além disso, as implementações oferecem um prompt interativo de onde se pode chamar as funções do programa diretamente, sem necessidade de escrever uma função main para testar funções internas do programa. O resultado é que se pode encontrar e corrigir os problemas do programa à medida em que ele é escrito, ao invés de escrever um código longo e "inteiro" e debugá-lo depois. C, nesse e em outros quesitos, segue uma filosofia "não ajuda mas também não atrapalha". Boa parte dos erros de tipos geram warnings ao invés de erros. A linguagem não força o tratamento de erros de execução (mas também não lhe diz que eles ocorrem, além da sucinta mensagem Segmentation fault). Hoje em dia as assim chamadas linguagens de script são mais flexíveis com programas incompletos, e muitas oferecem prompts interativos, embora estes costumem parecer afterthoughts*, especialmente comparados com o Common Lisp. Não matar o programa diante de erros e permitir continuar a execução ainda é uma idéia louca demais para essas linguagens. Java, entretanto, destaca-se entre as demais linguagens em ativamente impedir a execução de programas incompletos, não por razões técnicas, como em C, mas por filosofia da linguagem. Ironicamente, um dos co-autores da especificação do Java é o Guy Steele, um dos criadores do Scheme, autor do Common Lisp, the Language, e um dos principais membros do comitê de padronização do Common Lisp. A vida tem dessas coisas. * Appendix: o show de horroresDesculpem o rant. Pretendo que eles sejam esporádicos no blog, e escrever conteúdo mais útil/interessante/informativo a maior parte do tempo. Mas sendo ranting a função primária de um blog, é impossível suprimi-la totalmente. Humanitas precisa reclamar. E já que chegamos até aqui, vou aproveitar a atmosfera smug-lisp-weenie e fazer uma análise dos prompts interativos das linguagens de script mais populares. Python: Python é whitespace-nazi o suficiente para reclamar se o código estiver indentado mas não estiver subordinado a nenhuma estrutura de controle (e.g., a primeira linha de código de um programa nunca pode estar indentada). O prompt, obviamente, não é exceção. Como conseqüência, não se pode copiar um trecho interno de uma função e colar diretamente no prompt para testar. Além disso, o prompt interativo prestativamente imprime ... quando espera que um comando de múltiplas linhas seja completado, o que significa que não se pode copiar o trecho de código sem alterações e colar de novo no prompt (afinal seta-pra-cima anda pelo histórico linha por linha, não expressão por expressão) ou no código-fonte. Ruby: Não é whitespace-nazi, mas compartilha dos outros problemas. Perl: Segundo o FAQ da linguagem, a maneira típica de usar a linguagem interativamente é entrando no debugger, com um comando do tipo perl -de 42. O debugger não tem suporte a readline (i.e., sem seta-pra-cima). O debugger não imprime o resultado das expressões. Há comandos para imprimir, mas... DB<1> @a = (1,2,3) DB<2> p @a 123 DB<3> $a = [1,2,3] DB<4> p $a ARRAY(0x209d968) Perdeu pro GDB, hein? Lamentável. PHP: A versão linha de comando do php tem um "modo interativo", que consiste simplesmente em ler o código da stdin as usual, mas executar as expressões à medida em que são lidas. Sem prompt, sem histórico, sem impressão das expressões por default. E sim, tem que digitar o <?php antes do código. JavaScript: Eu ia avacalhar com o JavaScript por costume, mas depois das duas acima é complicado. O prompt do SpiderMonkey usa as conversões de string padrão do JavaScript, que transformam [[1,2,3],[4,5,6]] em "1,2,3,4,5,6", mas pelo menos é fácil de remendar. O histórico é linha-por-linha (aliás, o mísero e ignorado bash anda expressão por expressão no histórico), mas pelo menos o prompt não imprime um prefixo antes das linhas de continuação. E parece que as versões mais novas do SpiderMonkey imprimem os objetos numa sintaxe JSON-like, o que resolve o problema anterior e ganha do <main.Foo instance at 0x00234269> das linguagens acima. É, a coisa tá tão feia que o JavaScript ganhou de todo o mundo. A vida tem dessas coisas. |
Main menuPosts recentes
Tags
Elsewhere |
Copyright © 2012 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.