Elmord's Magic Valley

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

Blueprints for a shell, parte 4: Ramblings on syntax

2015-03-17 01:10 -0300. Tags: comp, prog, pldesign, shell, lash

Este post é parte de uma série sobre o lash, um shell que eu estou ideando.

Hoje discutiremos algumas questões sintáticas do shell. Depois disso eu provavelmente vou dar uma pausa na série e tentar implementar um protótipo do lash, mesmo com algumas questões ainda em aberto. Em particular, falta falar sobre estruturas de controle (mas o básico (if, while, each) não tem muito o que discutir) e módulos (que vão ficar para o futuro).

O meu objetivo ao escolher a sintaxe do shell é achar um ponto de equilíbrio entre minimalismo sintático total (e.g., S-expressions1) e ter sintaxe especial para tudo (e.g., bash). No geral, o guiding principle é expor a maior parte das funcionalidades do shell por meio de funções, e usar sintaxe especial apenas quando seria inconveniente escrever uma chamada de função, especialmente para features freqüentemente usadas em modo interativo (e.g., redirects e pipelines). Este post é uma survey dos elementos sintáticos do (ba)sh e como eles serão representados em lash.

Comandos simples

A sintaxe básica de um comando em (ba)sh é, em BNF fuleiro:

command ::= {var=value}* {word | redirect}*

A semântica é: se há words no comando, a primeira word é o nome do comando a ser executado, e as demais são os argumentos. O comando é executado em um ambiente acrescido das variáveis de ambiente especificadas, e com os redirects em efeito. Se não há words, as variáveis especificadas (do shell, não de ambiente) recebem os valores atribuídos, e os redirects... bom, aparentemente não fazem nada, mas isso depende da variante de sh, porque o comportamento aparentemente é indefinido no padrão POSIX. A ordem de avaliação das coisas também é um pouco peculiar:

bash# a=$(date >&2) uname $(pwd >&2) 2>/dev/null
/tmp
Mon Mar 16 21:27:09 BRT 2015
Linux

dash# a=$(date >&2) uname $(pwd >&2) 2>/dev/null
/tmp
Linux

Vale notar que os redirects e as words podem aparecer intercalados na linha de comando (inclusive minha BNF está errada, porque redirects podem aparecer intercalados com as atribuições também); a ordem em que eles aparecem relativos aos outros elementos sintáticos parece ser irrelevante.

Em lash, depois de muita hesitação, eu decidi atirar pela janela as atribuições prefixadas; o comando env do Unix já serve para rodar comandos em um ambiente modificado (env FOO=bar comando). Eu pensei em obrigar os redirects a aparecerem no final, mas me dei conta de que pode ser útil escrever um redirect intercalado em comandos que recebem blocos. e.g.:

each_line </etc/passwd {|line|
    echo "bla bla $line"
}

Ainda não sei até que ponto isso pode ser útil, mas por enquanto fica aí. Fica a questão da ordem de avaliação. A remoção das variáveis prefixadas são uma coisa a menos na equação. Quanto ao momento em que os redirects tomam efeito, há algumas possibilidades:

  1. Antes de tudo, afetando inclusive chamadas a comandos com $(...), $[...] e companhia. Tem o detalhe de que o redirect em si também pode envolver avaliação (ls >$[generate-a-file-name]). Nesse caso o redirect evidentemente só pode ter efeito depois do comando.
  2. Depois da avaliação de tudo e imediatamente antes de executar o comando propriamente dito. Aparentemente é isso que o bash faz.
  3. O redirect afeta a avaliação de tudo o que aparece depois dele na linha de comando, i.e., 2>/dev/null foo $(bar) afeta a execução de bar, mas foo $(bar) 2>/dev/null não.

Por ora o plano é fazer como o bash, primariamente porque sim.

Fica ainda a questão da atribuição, já mencionada anteriormente: usar um comando para atribuição (set x = 42), ou tratar o = especialmente no parser? Eu não gosto muito de casos especiais, mas talvez a atribuição mereça tratamento especial. Eu nem sei se atribuição (por oposição a definição de uma nova variável) é particularmente freqüente em um script para justificar um caso especial.

Quoting

O bash possui uma porção de coisas quote-like:

O plano para o lash é:

Outra utilidade de strings com delimitador (semi-)arbitrário é que elas supririam a funcionalidade dos "here-documents" do bash, os quais veremos adiante.

Here-documents

Here-documents permitem embutir um trecho de texto, delimitado por uma string à escolha, a ser enviado para a entrada padrão (ou outro file descriptor) do comando a ser executado:

cat <<FIM >foo.txt
The quick brown fox
jumps over the lazy dog.
FIM

Por padrão, o shell realiza substituições no conteúdo do here-document. Se o delimitador for citado/escapado, o conteúdo é interpretado literalmente. Além disso, se o delimitador é precedido de -, espaços e tabs no começo de cada linha são descartados.

Em alguma versão o bash introduziu também "here-strings", que permitem usar uma string simples ao invés de um documento multi-linha como entrada:

sed 's/foo/bar/' <<<"$content"

Se o lash adotasse um mecanismo para strings com delimitadores (semi-)arbitrários, como a contra-aspa descrita anteriormente, seria possível unificar esses dois casos. Strings com delimitador arbitrário podem ser usadas também para inicializar variáveis, por exemplo, coisa que não é possível com here-documents em bash.

Parameter substitution

O bash possui uma dúzia de coisas da forma ${varsomething}, que permitem fazer alguma transformação sobre o valor de uma variável. Além de a sintaxe ser abstrusa, a string a ser manipulada tem que estar armazanada em uma variável (não pode ser o resultado de outra substituição, por exemplo; para aplicar múltiplas substituições é necessário armazenar os resultados parciais em uma variável). O plano em lash é substituir todas as substituições (heh) por funções.

Existe um pequeno problema envolvido: o bash distingue entre ${var//$match/$replacement} e ${var//"$match"/$replacement}. No primeiro caso, *, ? e similares dentro de $match têm seus significados de globbing, enquanto no segundo eles são interpretados literalmente. Esse problema afeta outras coisas que trabalham com patterns. No comentário linkado (que trata da função glob, que retorna uma lista dos arquivos que casam com um padrão), a solução que eu encontrei foi usar uma format string para separar as partes que devem ser interpretadas como pattern das partes que devem ser interpretadas literalmente (assim como printf em C separa a string de controle de strings incluídas com %s e que são usadas literalmente), mas no caso de substituições não sei se seria muito conveniente – talvez agrupando o pattern e seus argumentos em um array:

# Equivalente a ${string//"$match"*/"$replacement"} em bash.

subst $string ("%s*" $match) $replacement

Kinda weird, mas eu consigo sobreviver. Na verdade, acho que o melhor seria tratar o pattern como literal por padrão, senão certo que alguém vai escrever $[subst $var $match $replacement] sem nem pensar se $match contém asteriscos ou não, e aí vai ser outra daquelas situações em que um script funciona 99% do tempo, até que um dia alguém resolve usar uma string com * e o script tem um comportamento inesperado. A sintaxe de subst poderia ser:

Qual a sua opinião?

Outra situação que usa patterns e sofre do mesmo problema é o case, que a princípio há de ser um comando comum sem sintaxe especial (case STRING (PATTERN-1 BLOCO-1 ... PATTERN-N BLOCO-N)2). Idealmente a sintaxe adotada para as substituições deverá ser utilizada para o case também.

And, or, not

Em (ba)sh, comando1 && comando2 executa comando1 e, se este retornar 0 (i.e., verdadeiro), executa comando2. O exit status do comando como um todo é o exit status do último comando que for executado. Analogamente, comando1 || comando2 executa comando1 e, se este retornar não-zero (i.e., falso), executa comando2. Em ambos os casos, comando é um "comando completo", que pode envolver pipelines. Há dois casos de uso principais desses operadores:

Portanto, eles permanecem.

! nega o exit status do comando (troca de não-zero para 0 e de 0 para 1). Ele também se aplica a um "comando completo", negando uma pipeline inteira (o exit status de uma pipeline é o exit status do último comando), e essa seria a única razão que eu vejo para tratá-lo como sintaxe especial e não apenas um comando chamado !. Não sei se justifica; além de ser uma situação bem rara, nada impede de simplesmente escrever o ! antes do último comando da pipeline. Além disso, talvez fosse o caso de escrever ! {comando1 | comando2} anyway, por clareza. While we are at it, podíamos renomear o comando para not, para deixar mais claro que se trata de um comando comum e não sintaxe especial, mas aí já não sei.

Process substitution

Em bash, <(comando) cria um pipe (um par de file descriptors em que tudo que entra numa ponta sai na outra), executa comando com a saída padrão redirecionada para o lado entrante do pipe, e a expressão é substituída por um nome de arquivo que corresponde ao lado de saída do pipe. Por exemplo, é possível escrever:

diff <(sort file1) <(sort file2)

que executa sort file1 e sort file2 e chama algo como diff /dev/fd/63 /dev/fd/62. Analogamente, >(comando) executa comando com a entrada padrão vinda da ponta de saída do pipe, e a expressão é substituída por um nome de arquivo correspondente à ponta de entrada.

Embora essa sintaxe seja bastante conveniente para usar na linha de comando (e na verdade acho que o exemplo com o diff é o único que eu já usei na linha de comando na vida), não sei se eu quero mantê-la em lash. Não só pelo princípio de evitar sintaxe extra gratuita, mas também porque ela parece um redirecionamento, mas é uma word. Se eu quisesse redirecionar um file descriptor para o resultado do process substitution (o que é útil primariamente para fazer um pipeline com um file descriptor que não seja a stdout, e.g., redirecionar a stderr para um comando), eu teria que escrever algo como (o espaço é necessário):

ls 2> >(comando)

o que não é exatamente óbvio. Talvez uma função desse conta do recado, algo como:

diff $[popen -r {sort file1}] $[popen -r {sort file2}]

Ok, a cara disso é terrível3. Talvez se a popen ganhar outro nome, e o comando aceitar um nome de comando e argumentos diretamente ao invés de obrigatoriamente um bloco:

diff $[readfrom {sort file1}] $[readfrom {sort file2}]
diff $[pipefrom {sort file1}] $[pipefrom {sort file2}]
diff $[pipefrom sort file1] $[pipefrom sort file2]

Não sei.

Outro problema com a sintaxe do bash é que o comando parece um array, e talvez um array fizesse sentido como alvo do redirect (redirecionaria para todos os nomes de arquivo no array). Por outro lado, o caso do array poderia ser representado pelo array "spliced", qualquer que seja a sintaxe escolhida para ele (e.g., >$@(file1 file2)), ou simplesmente permitindo múltiplos redirects do mesmo file descriptor (>file1 >file2; o zsh permite isso, acho). Não sei.

Humanitas precisa dormir

Por hoje ficamos por aqui. Como sempre, tudo o que eu digo que "é" de tal jeito é só o plano atual, tudo está sujeito a discussão, comentários e sugestões são sempre bem-vindos, live free or die, do what you want 'cause a pirate is free, etc. Como esse é, a princípio, o último post da série for a while, sinta-se a vontade para comentar aqui sobre tópicos não abordados até agora na série.

_____

1 Em tempos de outrora eu pensei em usar S-expressions para toda a sintaxe (inclusive redirecionamentos e pipelines), mas permitir omitir os parênteses em torno de comandos que aparecem sozinhos em uma linha. O resultado não me foi exatamente satisfatório. Além disso, turns out que um shell totalmente baseado em S-expressions já foi feito (o qual por sinal provavelmente é uma boa fonte de inspiração).

2 Os patterns e blocos vão em um array primariamente para permitir que eles ocupem múltiplas linhas sem ter que pôr um \ no final de cada linha:

case $file (
    "*.mp3" { ... }
    "*.ogg" { ... }
    "*" { ... }
)

3 Revisando o post, eu olhei para isso e pareceu a sintaxe mais natural do mundo, mas a essa altura minha percepção já está meio alterada pelo sono.

7 comentários

Blueprints for a shell, parte 3: Tipos de dados

2015-03-13 22:47 -0300. Tags: comp, prog, pldesign, shell, lash

Este post é parte de uma série sobre o lash, um shell que eu estou ideando.

A world made of strings

Em (ba)sh só existe um tipo de dado: a string. Em bash, uma variável pode ser declarada como um array (e em versões mais recentes, como um dicionário), mas embora a variável seja um array, o array em si não é um valor de primeira classe: não é possível passar um array como argumento para uma função, ou armazenar um array dentro de outro, por exemplo. Isso limita um bocado o que se pode fazer em bash sem apelar para gambiarras do inferno. (Claro que "dá" para viver sem essas coisas. Também "dá" para programar com máquinas de Turing...)

lash quebra com a tradição, se revolta contra o sistema e introduz arrays, dicionários e blocos de primeira classe (bem como possivelmente outros objetos, como canais de comunicação, mas isso ainda está em aberto). Assim, é possível fazer coisas futurísticas como manter uma coleção de dados estruturados e escrever funções para manipular arrays e produzir outros arrays. Fantástico, não? Welcome to 2015.

Independentemente do shell, variáveis de ambiente e argumentos de processos no Unix também são strings (e strings que não podem conter \0, ainda por cima), o que significa que não temos como passar diretamente nossos valores estruturados para outros processos. Uma abordagem alternativa seria fazer como Tcl: representar tudo como strings, definir certos formatos de string para armazenamento de dados estruturados (e.g., keyed lists, ou XML if you're feeling crazy), e prover funções para interpretar e manipular tais strings. Isso permitiria passar dados "estruturados" para subprocessos, pois eles seriam apenas strings. Mas, seriously, guardar tudo como string e parsear/procurar dentro da string para obter um elemento de uma lista/dicionário? Gerar uma string nova toda vez que se altera um elemento? Tá certo que seria possível mitigar um pouco esses problemas usando alguma representação interna mágica para strings, mas sei lá. Por ora eu prefiro ter dados estruturados normais.5 Além disso, blocos têm que ser dados especiais de qualquer forma, para carregar informação de escopo.

So, tipos de dados.

Strings e números

Uma string em lash é uma seqüência de bytes; internamente, o shell não está preocupado com a interpretação desses bytes (como caracteres codificados em UTF-8, por exemplo). No geral, o ambiente Unix como um todo não está preocupado com o conceito de codificação; nada exige que nomes de arquivo sejam strings UTF-8 válidas, por exemplo, e o resultado de um globbing deveria ser representável por strings do shell sem nenhum mistério. Arquivos/streams também não tem nenhuma codificação inerente, e coisas como echo $str não deveriam ter que fazer nada de mágico para decidir como mandar o conteúdo da string para o arquivo. Interpretar os bytes de uma string como UTF-8 (ou outro encoding) é responsabilidade das funções que o shell provê para manipular strings.

Acho que em um shell não faz muito sentido ter um tipo numérico distinto. Em um shell, quando se escreve algo como my x = 01, espera-se que o 0 permaneça lá; quando se chama xargs -0, espera-se que o - não se perca, etc. Além disso, os argumentos que o script recebe da linha de comando são todos strings, e não me parece interessante ter que convertê-los manualmente para números antes de fazer operações aritméticas com eles. Ao invés disso, a interpretação de uma string como um número cabe aos operadores aritméticos. Por questão de eficiência, o resultado de uma operação aritmética pode ser armazenado internamente como um número (a idéia é evitar ter que converter o resultado para string e reconverter para número caso ele seja usado novamente em uma operação aritmética), mas isso não é observável pelo script.

Diferentemente do (ba)sh, o lash deverá suportar aritmética de ponto flutuante. Isso levanta a questão de como distinguir divisão inteira de divisão em ponto flutuante. Eu sou favorável a adotar / para divisão em ponto flutuante e // para divisão inteira, a la Python 3. Os demais operadores aritméticos produzem resultado em ponto flutuante se um dos argumentos for float, e inteiro caso contrário. A representação em string de um número em ponto flutuante sempre inclui um ponto1 (a idéia é que se alguma coisa estiver produzindo resultados float indevidamente, isso não vai passar silenciosamente durante a execução (ou assim se espera)). Operações aritméticas sobre strings que não são números válidos produzem um erro de execução, i.e., nada de NaN propagation a la JavaScript ou interpretação implícita como 0 a la PHP. Na verdade nem o bash deixa esse tipo de coisa passar em silêncio... com algumas exceções: uma string vazia é tratada como um 0, e espaços em torno de um número são ignorados. Aqui fico na dúvida entre "strictness" e conveniência; talvez em um script seja uma boa aceitar esses dois casos.

Strings não são arrays, e (assim como em bash) não são indexáveis com a sintaxe normal de arrays. Haverá funções para obter substrings, mas ainda não pensei bem nos nomes e na sintaxe, e em como especificar o range de bytes/caracteres desejado (início e tamanho? início e fim? inclusivo ou exclusivo? Todas as opções, dependendo dos parâmetros?). Uma possibilidade seria:

Pode ser meio verboso, mas captura de substring parece ser uma coisa relativamente rara em bash, baseado em um grep na minha amostra extremamente significativa de meia dúzia de scripts que estavam à mão, então acho que a clareza e a flexibilidade compensam a verbosidade.

O tamanho da string pode ser obtido com as funções bytelen e charlen, dependendo do tipo de tamanho desejado. (Há ainda a situação em que se quer a largura impressa da string (combining characters não contam no comprimento, e caracteres chineses-et-al ocupam duas posições), bem como substrings baseadas na posição impressa dos caracteres, mas isso vai ficar para o futuro distante, possivelmente numa biblioteca.)

Funções que trabalham com delimitadores (e.g., split STRING DELIM) têm que aceitar delimitadores de tamanho arbitrário, pelo simples fato de que elas têm que funcionar com delimitadores em UTF-8 e ao mesmo tempo se manterem agnósticas quanto à codificação. (Por outro lado, isso assume que a codificação tem a mesma propriedade do UTF-8, de que é possível identificar o começo de um caractere inambiguamente a partir de um ponto arbitrário na stream, o que basicamente só é verdade no UTF-8 e em encodings em que 1 byte = 1 caractere. Meh.)

Arrays

Arrays são seqüências de valores quaisquer. A sintaxe literal para arrays é (valor1 ... valorN). (Os parênteses são herdados da sintaxe de inicialização de variáveis-array do bash. Além disso, colchetes e chaves já têm outros usos. Isso a princípio conflita com a sintaxe do (ba)sh para rodar um comando em um subprocesso4 (( comandos )), mas eu já não pretendia ter essa sintaxe em lash to begin with. Uma função poderia prover essa funcionalidade (e.g., subproc { comandos }).)

Arrays são indexados com a sintaxe $var[expr]. Assim como em bash, expr é avaliado como uma expressão aritmética, sem necessidade de escrever $var[$((expr))]. Diferentemente de bash, chaves não são exigidas, i.e., não é necessário escrever ${var[expr]}. Por um lado isso é mais limpo, mas por outro pode conflitar com o uso de [] como wildcard, e.g., my prefix = /dev/tty; echo $prefix[1-8]. Acho que isso não chega a ser um grande problema, pois isso gera um erro de execução ($prefix não é um array), e portanto é fácil de detectar e corrigir (para ${prefix}[1-8]; dá até para incluir essa informação na mensagem de erro).

Assim como em bash, o array tem que estar em uma variável para ser indexado ($[função][expr] não seria interpretado como uma indexação do resultado de função, a princípio (ou seria?)), mas nada impede que haja uma função index ARRAY N, com a qual se poderia escrever $[index $[função] N].

A sintaxe de atribuição funciona com arrays também (var[i] = 42). Isso implica que atribuição tem que ter tratamento sintático especial, para que coisas como var[i*i] = 42 não causem globbing.

Como fica o caso de arrays multidimensionais (i.e., arrays que contêm outros arrays)? $var[i][j] é uma sintaxe válida? Se sim, não tem por que não aceitar $[função][expr] também, acho.

É possível atribuir a uma posição que ainda não existe (a la Perl), ou isso é um erro (a la Python)? Se a "label" do índice é importante (e não apenas a ordem), não seria o caso de usar um dicionário anyway? Eu consigo pensar em duas situações em que se poderia querer especificar um índice não-existente explicitamente:

  1. Adicionar um elemento no fim do array. Mas para esse caso poderia haver uma função push (ou append, porque aí também podemos ter uma prepend para adicionar no começo; ou poderia haver uma função mais geral insert, para inserir um elemento entre dois quaisquer, ou no início/fim), ou uma sintaxe a la PHP (var[] = 42).
  2. Inicializar um vetor/matriz com alguma fórmula matemática, e.g.:
    my array = ()
    range 0 -toin 10 {|i|
        array[i] = $(( i * i ))
    }
    

    Parece um caso de uso razoável, mas de qualquer forma ele falha com arrays multidimensionais ($array[i][j] = 42 é um erro porque $array[i] não é um array, a menos que seja inicializado primeiro). Pode-se suprir esse caso com uma função make_matrix que recebe o tamanho das dimensões e retorna um vetor inicializado.

Ou podemos permitir atribuição out-of-bounds (e preencher qualquer elemento entre a última posição preenchida e a posição atribuída com a string vazia) e era isso. Não sei (o plano inicial é não permitir).

Outra função básica de manipulação de arrays é each, que recebe um array e um bloco e chama o bloco com cada elemento do array. Também pode haver uma map, que produz um novo array com cada resultado retornado pelo bloco, e uma versão destrutiva de map (chamada map!, talvez2).

A função len retorna o número de elementos do array. Não sei se há necessidade de uma sintaxe especial para isso (e.g., $#var).

$@var "splices" o array, produzindo um argumento ("word" na terminologia do (ba)sh) para cada elemento do array, i.e.:

my array = (1 2 3)
foo $array         # chama foo com um argumento (o array)
foo $@array        # chama foo com três argumentos (1, 2 e 3)

Dicionários

Um dicionário é um mapeamento de strings para valores. (Por que só strings? Talvez faça sentido permitir valores quaisquer como chave.) A sintaxe literal para dicionários é %(chave1=valor1 chave2=valor2 ...) (o % é para sugerir uma vaga relação com hash-tables em Perl), com espaços opcionais em torno do =, o que fica meio estranho sem delimitadores entre os pares chave = valor, mas pode-se usar quebras de linha se desejado:

my person = %(
    name = Hildur
    age = 18
    country = Iceland
)

[Note to self: Em coisas como %(foo=(1 2 3)), assim como em my foo=(1 2 3), foo=(1 2 3) não é uma "palavra" normal do shell, porque é parte string, parte array, i.e., tanto dicionários literais quanto declaração de variável exigem tratamento especial pelo parser (a menos que haja um tipo de dados "associação" ao qual coisas da forma A=B possam ser mapeadas).]

Elementos de um dicionário são acessados com a sintaxe $var{chave}. Não se usa colchetes como em arrays porque a expressão entre colchetes sofre avaliação aritmética, que não é o que queremos em um dicionário. (Será que foi uma boa idéia fazer avaliação aritmética automática after all?) Isso é outro elemento de sintaxe (além dos blocos) que conflita com a sintaxe de brace expansion do bash (foo{1,2,3}). Não sei se isso é um ponto a favor da mudança da sintaxe de acesso a dicionário ou do brace expansion. Outra possibilidade seria usar colchetes, assim como arrays (e aí eles perdem a propriedade de avaliação aritmética, o que pode tornar o acesso a array meio inconveniente), ou talvez $var<chave>, mas isso conflita com a sintaxe de redirecionamento. (Lembrando que isso poderia ser um redirecionamento se $var contivesse um file descriptor. Nesse caso o > posterior seria um erro de sintaxe, então só a interpretação como acesso a dicionário seria válida, mas eu só descubro isso quando chego no >; além disso a chave não poderia ter um espaço não-escapado. Fora que é uma sintaxe totalmente não-usual para acesso a dicionário (as chaves pelo menos têm precedente em Perl).)

Se my dict = %(a=1 b=2 c=3), qual o resultado de $@dict?

Haveria uma porção de funções para iterar sobre dicionários: each-key; each-value; each-entry, que reberia um dicionário e um bloco de dois argumentos e o chamaria com a chave e o valor de cada entrada no dicionário; ou, havendo o tipo associação, chamaria o bloco com cada associação. Alternativamente, havendo o pipeline de objetos, poderia haver uma função keys que produz todas as chaves, e aí escreveríamos keys $dict |> each {|key| ... } (ou qualquer que seja a sintaxe do pipe de objetos), e da mesma forma para os valores (e associações, em as havendo).

Será que é uma boa ter um tipo dicionário distinto de array, ou o melhor é unificar os dois a la PHP, JavaScript, etc.? Acho que eu prefiro ter dois tipos separados, mas há de se pensar melhor.

Interações entre valores estruturados e strings

Em (ba)sh, diferentemente das linguagens de programação em geral, uma variável pode aparecer como parte de uma "palavra" maior, e.g., foo$bar; o conteúdo da string é concatenado na palavra e era isso. Mas e se $bar não for uma string? Pode-se produzir uma versão serializada do valor (o que provavelmente é mais útil), ou gerar um erro.

Coisas como foo$@bar (onde my bar = (1 2 3)) poderiam expandir para foo1 foo2 foo3, como o brace expansion do bash. O problema é que $@ assume que o array está em uma variável. Daria para expandir arrays literais também3, e,g., foo(1 2 3) geraria foo1 foo2 foo3, e aí seria possível eliminar o uso de chaves para brace expansion. O problema é que by far o meu uso mais freqüente de brace expansion na linha de comando é com a string vazia, e.g., mv file{,~} ao invés de mv file file~, e na nova sintaxe isso seria mv file("" ~) (na verdade o ~ teria que ser escapado para não sofrer tilde expansion...). Talvez dê para sobreviver.

^D

Por hoje ficamos por aqui. Como sempre, tudo o que foi apresentado são só os planos e idéias atuais, tudo pode ser mudado, e comentários e sugestões são muito bem-vindos (mas provavelmente só vou ver/responder comentários depois do fim-de-semana).

_____

1 Ou talvez um e+42 da vida (talvez só como formato de entrada válido, mesmo que as operações do shell sempre produzam resultados em notação decimal).

2 (update) Ou adicionar uma opção -overwrite à função map (que parece uma coisa mais shell-like); ou ainda, adicionar opções -collect e -overwrite à each e nem ter uma map separada.

3 (update) Note' to self: Isso também é uma string misturada com um array, então o my x=(1 2 3) não é mais um caso especial para o parser (ou pelo menos para o "reader", porque ainda teria uma interpretação diferente do caso foo(1 2 3)).

4 (update) Na verdade não conflita, porque um array não faz sentido como primeira coisa na linha de comando (ou faz?).

5 (update) Parafraseando um grande sábio, "If you want Tcl, you know where to find it." (Dito isso, eu vejo mérito na abordagem "everything is a string".)

4 comentários

Blueprints for a shell, parte 2: Variáveis, definições e escopo

2015-03-13 00:11 -0300. Tags: comp, prog, pldesign, shell, lash

Este post é parte de uma série sobre o lash, um shell que eu estou ideando.

Um pouco de contexto

Em (ba)sh todas as variáveis são globais (inclusive as "locais", que são globais com escopo dinâmico). Independentemente das variáveis do shell, todo processo no Unix possui um conjunto de variáveis de ambiente (environment variables). Os shells tendem a unificar variável do shell e de ambiente de alguma forma. A maneira como isso é feito em (ba)sh é tratar todas as variáveis uniformemente como "do shell" e marcar certas variáveis como "exported": essas variáveis são passadas como variáveis de ambiente para os processos chamados pelo shell. Além disso, o bash possui um comando local, que faz com que os valores atribuídos às variáveis passadas ao local a partir desse ponto só durem até a função onde o local foi chamado retornar, i.e., o local permite "shadowar" uma variável durante a execução de uma função. Funções chamadas pela função que declarou a variável "local" também vêem o novo valor, e nada impede "localizar" uma variável de ambiente (que continua sendo uma variável de ambiente).

Nessa situação, determinar a que variável o código está se referindo ao dizer $x é uma questão bastante simples: só existe uma variável x no programa inteiro. Evitar conflitos de nomes é basicamente problema do programador.

Se isso já é um problema em bash, em um shell com lambdas isso seria um disastre, pois um bloco de código pode ser chamado dentro de uma função diferente da que o definiu, e quem escreve o bloco não necessariamente tem como saber (nem deveria ter que saber) os nomes das variáveis usadas nesse outro ponto do programa. Assim, lash adota escopo léxico, como qualquer linguagem sã, o que significa que pode haver múltiplas variáveis com o mesmo nome em um programa. Isso também implica que nós vamos ter que conciliar escopo léxico com variáveis de ambiente.

So, variáveis em lash

O comando my introduz variáveis léxicas, cujo escopo é o bloco onde o my se encontra. A sintaxe básica é:

my nome = valor

Eu estou meio na dúvida quanto ao uso de espaços em torno do =. Em bash, atribuição de variável não permite espaços. Não havendo espaços, seria possível definir múltiplas variáveis no mesmo comando:

my x=1 y=2 z=3

Com espaços, para a coisa continuar legível, acho que seria necessário introduzir um delimitador entre as atribuições, mas isso não é tão simples em um shell, porque em:

 my x=1, y=2, z=3

a vírgula poderia ser parte da string que se está atribuindo. Uma alternativa é permitir declarar uma única variável com espaços, ou múltiplas variáveis sem espaços. A sintaxe não é ambígua, de qualquer forma.

Pergunta: uma definição com my x=1 afeta referências a x no mesmo bloco que apareçam antes do my? Por exemplo, em:

my x = 1
while {true} {
    echo $x
    my x = 2
    echo $x
}

que x é visto pelo primeiro echo quando o while executar pela segunda vez? Ou, de maneira mais convoluta:

my x = 1
my block = {
    my f = { echo $x; }
    my x = 2
    $f
}

imprime o valor de qual x? Se o desejado for o 1, então a implementação de variable lookup tem que tomar o cuidado de não simplesmente pegar o primeiro x subindo na hierarquia de ambientes (a princípio o bloco interno procuraria a variável x primeiro no ambiente do próprio bloco, depois no bloco em que o bloco se encontra, depois fora dos blocos). Por outro lado, essa semântica em que a referência a uma variável nunca muda, independente de declarações posteriores, permitiria resolver tudo estaticamente, o que pode deixar o lookup com uma performance melhor. Outra questão é: esse tipo de coisa acontece na prática? Eu fico seriamente tentado a dizer que é indefinido nesses casos qual das duas variáveis é acessada. Provavelmente alguém vai querer comer meu fígado por introduzir comportamento indefinido em um shell, mas eu não estou propondo nada da natureza de comportamento indefinido em C, em que o programa pode fazer qualquer coisa, incluindo roubar seu dinheiro e fugir do país; certamente uma das duas variáveis é acessada, sem nenhum efeito inesperado. A idéia é apenas manter em aberto a possibilidade de diferentes implementações de lookup de variáveis. Se você acha que isso é uma má idéia, por favor se manifeste.

Atribuição

Estou na dúvida se atribuição vai usar uma keyword do tipo set, ou se só o sinal de igual vai ser suficiente. Parece concebível que alguém invente um comando que recebe = como argumento, então:

foo = 42

poderia ser uma chamada a foo. Esse problema poderia ser evitado exigindo set foo = 42, ou proibindo os espaços em volta do = (que é o que o (ba)sh faz), mas o espaço me parece bem desejável quando o valor atribuido é uma expressão maior com chamadas a funções e what-not, ou quando o lado esquerdo é um array[índice]. Por outro lado, não lembro de nenhum comando que recebe = como primeiro argumento, então talvez tratar um = não escapado/quoted na segunda posição como algo especial e dispensar o set não seja problema. Será?

Também há de se considerar a possibilidade de introduzir outros operadores de atribuição, como +=, e nesse caso, se haverá operadores separados para strings, números e arrays ou se um só basta. (Em bash, += appenda strings e arrays; olhando o lado direito da atribuição dá para saber qual é o caso. Para incrementar variáveis numéricas, é necessário estar em "modo de expressão aritmética", i.e., dentro de ((...)), $((...)), índice de array, etc.)

O que acontece ao se atribuir um valor a uma variável não declarada? Acho que isso seria no mínimo um warning, talvez um erro. Acessar uma variável não-definida também, mas seria bom ter alguma coisa equivalente ao ${var:-default}, i.e., "usa o valor de $var, ou a string default caso var não esteja definida (ou seja vazia, se o : estiver presente)". Eu tinha pensado em ter uma função or valor1 valor2, que devolve valor1 se ele for um valor diferente da string vazia (ou um valor nulo especial? nós teremos um?), ou valor2 caso contrário. O problema é que $[or $var default] vai emitir um warning se $var não estiver definida. Talvez pudesse haver uma sintaxe especial $?var que devolve o valor da variável ou vazio caso ela não exista, sem emitir um warning, e então o equivalente do ${var:-default} seria $[or $?var default]. Meio verboso, mas não parece ruim (eu acho).

Variáveis globais

Nós teremos um sistema de módulos (cujos detalhes eu ainda não pensei direito e que será assunto de um post futuro), e concebivelmente um módulo poderá querer tornar algumas variáveis visíveis a outros módulos. Possibilidades:

Separar variáveis públicas das demais parece uma boa, mas não sei se não é "só uma coisa a mais".

Funções

Funções e variáveis vivem em namespaces separados em (ba)sh, e a princípio isso deve ser mantido em lash. Em (ba)sh, todas as definições de função possuem escopo global (na verdade tudo tem escopo global em (ba)sh). Como já comentado anteriormente, embora possa parecer "óbvio" mudar isso em lash e tornar as definições de função léxicas, assim como as variáveis, código como:

if {some-condition} {
    def foo {
        ...
    }
}

em que se espera que a definição de foo resultante seja global, é comum em arquivos de configuração e afins. Possibilidades:

  1. def define funções globais, i.e., no escopo do módulo em que a definição foi feita. (No escopo léxico, ou no escopo dinâmico? Se um bloco que contém um def é passado como argumento e chamado em uma função definida em outro módulo, em que módulo o def tem efeito? Bom, a julgar pelo if, no módulo em que o def se encontra, i.e., no escopo léxico.) Não há definições locais de função e era isso.
  2. def define funções globais, mas é possível escrever algo como my def foo { ... } para definir uma função local. Pode ser uma boa, só não sei se vale a pena o esforço. Também teria algum efeito no lookup de funções/comandos que precisa ser melhor considerado.
  3. def define funções no escopo léxico local. Bagunça com o caso do def dentro de um if, mas isso poderia ser contornado permitindo algo como public def foo { ... } dentro do if. (Mas quem disse que eu queria exportar do módulo? Também poderia ser usada uma keyword diferente (e.g., global), que torna global mas não exporta do módulo.)

No momento eu estou inclinado à alternativa (1), mas aceito contra-argumentos.

Funções definidas em um módulo são visíveis a partir de outros módulos por default, ou é necessário dizer public def foo { ... } para exportar uma função? (Lembrando que a gente nem decidiu ainda se vai ter uma keyword public ou não na linguagem...)

Variáveis de ambiente

O escopo de uma variável de ambiente a princípio é o processo inteiro. (É possível conceber que cada módulo pudesse ter sua própria idéia de ambiente, mas acho que nunca antes na história desse país uma linguagem tratou variáveis de ambiente assim.) Em um shell, espera-se acessar variáveis de ambiente com a mesma sintaxe das variáveis comuns (acho inventar uma sintaxe nova para dizer $HOME não vai ser uma proposta popular). Outra peculiaridade das variáveis de ambiente é que seus valores só podem ser strings. Seria possível serializar outros valores para permitir passá-los como variáveis de ambiente para subprocessos, mas só o lash reconheceria essas variáveis como valores especiais, e seria necessário indicar de alguma maneira reliable que a variável contém um valor especial, e não uma string que parece muito com um valor especial. Depois do causo do ano passado com o Shellshock, eu estou meio receoso de permitir coisas que não sejam strings em variáveis de ambiente.

Em bash uma conseqüência não muito agradável de o shell misturar as variáveis de ambiente com as comuns é que é possível um script começar a usar uma variável feliz da vida sem saber que havia uma variável de ambiente com o mesmo nome. Isso é agravado pelo fato de que em bash uma variável inexistente pode ser usada sem warning nem erro (a menos que set -u esteja ativo), então um script pode ser escrito assumindo que uma dada variável está vazia e inadvertidamente herdar do ambiente uma variável com conteúdo. Mesmo que esse não seja o caso e o script inicialize suas variáveis antes de usar, ele ainda pode estar inadvertidamente alterando uma variável de ambiente, que será herdada por subprocessos.

Em lash a situação a princípio é menos problemática porque toda variável tem que ser declarada antes de usar, e um my sobrepõe uma variável de ambiente de mesmo nome. Em geral, se eu esquecer de declarar a variável, o shell emitirá um erro, então um script que roda sem erros para mim pelo menos está imune a variáveis de ambiente inesperadas presentes nos sistemas dos outros, mas eu ainda posso acabar esquecendo o my sem gerar erro se der o acaso de eu usar um nome de variável que é uma variável de ambiente presente no meu sistema. Soluções:

  1. Exigir que toda variável de ambiente usada seja explicitamente importada antes do uso. Acho que isso não seria uma opção muito popular. Talvez não fosse tão ruim se algumas variáveis mais tradicionais fossem importadas por default (e.g., HOME, USER), mas isso me parece super-arbitrário.
  2. Permitir o acesso a variáveis de ambiente como qualquer outra variável, mas permitir atribuição apenas com um comando especial (e.g., setenv HOME = /). Acho que isso pega como erro a grande maioria das capturas indevidas de variáveis de ambiente. Fica o caso de se o programador erra o nome da variável de ambiente (uma nova variável seria criada, ao invés de emitir um erro). Evitar esse problema acho que traria mais inconveniente do que vantagem.
  3. Não fazer nada. Na real isso mal é uma opção, já que o setenv tem que existir de qualquer forma para criar variáveis de ambiente novas, e uma vez que ele exista não tem por que não aplicar a solução (2).

So (2) it is, aparentemente.

Escopo dinâmico

E quando eu quero escopo dinâmico, after all? Pode-se argumentar que ninguém em sã consciência quer escopo dinâmico, mas, por exemplo, se formos implementar o tal pipeline de objetos, precisamos de um meio de redirecionar o canal de saída de um comando para o canal de entrada de outro, e uma maneira de fazer isso é ter os canais de entrada e saída como variáveis dinâmicas e shadowá-las para fazer o redirecionamento; é como normalmente se redireciona *standard-output* e companhia em Common Lisp, e (current-output-port) et al. nos Schemes que suportam "fluid variables" (que são variáveis dinâmicas com outro nome).

Se formos ter variáveis dinâmicas, para evitar o caos manifesto, parece uma boa exigir que elas sejam previamente declaradas como tal (i.e., não é possível "localizar" a la bash uma variável previamente declarada com my). Também há o problema de como implementar o escopo dinâmico. Na situação em que só há uma thread, a operação de shadowar uma variável pode ser implementada simplesmente salvando o valor antigo, atribuindo o valor novo, e depois restaurando o valor antigo. Quando há múltiplas threads, entretanto, deseja-se que um shadow dentro de uma thread não afete as outras. E guess what? O nosso pipeline de objetos exige que cada parte do pipeline rode simultaneamente (ou pelo menos cooperativamente), dentro do mesmo processo, e o que cada uma vê como canal de entrada e de saída é diferente, então essa implementação "ingênua" de shadowing não nos serve.

Eu tenho um certo receio de que, a menos que as variáveis dinâmicas sejam identificáveis estaticamente, a presença delas bagunce / afete a performance do lookup de todas as variáveis. Quando a definição da variável dinâmica está lexicamente visível é fácil distingui-las, mas quando elas vêm de outro módulo, isso pode ser complicado. Uma solução é simplesmente usar uma sintaxe diferente para acessar variáveis dinâmicas, e.g., earmuffs: $*output_channel*. Essa sintaxe tem a vantagem de ser imediatamente familiar ao grande contingente de programadores de Common Lisp (right?), e a desvantagem da potencial confusão com o * que faz globbing (e.g.:

dynamic *prefix* = foo
touch foo1 foo2 foo3
echo $*prefix**

), mas outra sintaxe que distinguisse variáveis dinâmicas de variáveis comuns poderia ser escolhida.

Acho que por hoje deu

Reiterando, sempre que eu digo que alguma coisa em lash "é" de tal e tal jeito, eu só quero dizer que esse é o plano atual, mas estou aberto a sugestões. Feedback é sempre bem-vindo.

8 comentários

Blueprints for a shell, parte 1: Funções, blocos e retorno

2015-03-11 23:15 -0300. Tags: comp, prog, pldesign, shell, lash

Este post é parte de uma série sobre o lash, um shell que eu estou ideando.

Hoje vamos discutir a feature que dá nome ao shell, lambdas, ou blocos. (Na verdade eu pensei no nome primeiro e fiquei com ele porque consegui pensar num significado que o justificasse, mas não vamos nos ater a esses detalhes.)

(Em diversos pontos ao longo do texto eu vou dizer que certa feature em lash "é" de tal e tal jeito. Isso só significa que essa é a minha idéia atual sobre a feature, não que eu tenha decidido definitivamente que isso vai ser assim. Comentários e sugestões são sempre bem-vindos.)

Como mencionado anteriormente, a idéia em lash é usar blocos extensivamente ao invés de sintaxe especial para estruturas de controle (if, for, while, etc.). Blocos em lash são valores de primeira classe, i.e., podem ser passados como argumento para funções, por exemplo. Um bloco instanciado é uma closure, i.e., ele lembra do ambiente de variáveis em que foi criado. No geral, variáveis em lash têm escopo léxico, e não escopo dinâmico como em (ba)sh. (A coisa não é tão simples por conta de variáveis de ambiente e outros detalhes, mas discutiremos isso no futuro.)

Blocos são escritos entre chaves ({ comandos }). Blocos podem receber parâmetros, que podem ser declarados com uma sintaxe Ruby-like: {|param1 param2 ... paramN| comandos }. O último parâmetro pode ser precedido de @; nesse caso, ele coleta em um array os argumentos restantes da chamada ao bloco.

Não sei se permitir $1, $2, etc., para acessar os argumentos de um bloco é uma boa idéia; como tudo é bloco em lash, acho que isso ia dar muita confusão ao tentar acessar um argumento de função de dentro de um if e situações similares. Melhor é requerer que os parâmetros sejam declarados. ($1 e companhia talvez possam adquirir outros usos, e.g., em matching de expressões regulares, mas esse é um tópico que eu não vou abordar any time soon.)

Now the thorny questions.

Arity mismatch

O que acontece se o número de parâmetros e de argumentos não casar? No geral o ideal é gerar um erro de execução ou um warning, mas eu me pergunto se não há situações em que pode ser interessante permitir passar um bloco sem parâmetros para uma função que chama o bloco com alguns argumentos, nos quais o bloco não tem interesse. (Por exemplo, o if poderia chamar o bloco do "then" com o resultado retornado pelo teste do if, no qual não temos interesse a maior parte do tempo.) Uma possibilidade seria não permitir mismatch, exceto no caso em que o bloco não tem declaração de parâmetros at all, i.e., {|| true; } 42 é um erro, mas { true; } 42 não é. Mas eu imagino que isso possa fazer funções declaradas sem parâmetros engolirem silenciosamente argumentos passados por engano. Por ora, acho que mismatch vai ser sempre um erro/warning mesmo, enquanto não aparecer um caso de uso que definitivamente sugira que o contrário é desejável.

Retorno

Quando eu digo return 42, quem retorna? O comportamento esperado é retornar da função em que o return se encontra, mas agora o corpo de um if ou foreach tecnicamente também é uma função, que provavelmente não é a função que o usuário tem em mente ao escrever um return.

Se o return retorna da função "esperada", também há o caso em que um bloco que contém um return é passado para uma função definida pelo usuário e chamada de dentro dessa função; nesse caso o return é um non-local exit, i.e., a função que retorna é a função onde o bloco foi definido, não a função que chamou o bloco. (Na verdade o caso do return dentro do if também é um non-local exit, mas é um caso com o qual nós já estamos acostumados.) Outros casos de controle de fluxo não-local são os comandos break e continue dentro de um while. Talvez fosse interessante introduzir uma construção mais geral a partir da qual esses casos mais específicos podem ser implementados, e que também poderia ser usada para implementar exceções. Ao mesmo tempo, eu gostaria que um return fosse uma operação "barata", então é necessário tomar algum cuidado antes de sair over-engineerando controle de fluxo. A construção que naturalmente "suggests itself" para a tarefa é continuations e call/cc, mas esse caminho me dá um certa preocupação, especialmente se continuações que retornam múltiplas vezes forem permitidas. (Incidentalmente, eu pretendo implementar as versões iniciais do shell em Chicken Scheme, o que tornaria tudo isso muito simples, mas eu quero manter aberta a possibilidade de reimplementar em alguma outra linguagem no futuro (e.g., Rust, depois que ele sair de alpha).) Além disso, seria necessário lidar com unwind-protect / dynamic-wind / interação de tratadores de exceção com continuations. Eu não estou gostando muito de toda essa complexidade que surgiu do nada enquanto eu estava tranqüilo aqui inventando meu shell.

Outra dificuldade é como fazer o return, que a princípio seria um comando como qualquer outro, retornar do bloco lexicamente apropriado, já que ele não recebe como argumento nada que lhe sirva para saber de que escopo léxico ele foi chamado. Ele não pode só retornar do contexto mais no topo da pilha de chamadas porque o return pode ser não-local. Por exemplo, em um código como:

def foo {
    bar {|x| return $x}
}

def bar {|block|
    $block 42
}

o return que será executado quando $block for invocado deve retornar de foo, não de bar. Uma solução é fazer todo comando receber implicitamente um argumento escondido que representa o escopo em que o comando foi chamado. That's kinda weird (e me lembra o &environment das macros do Common Lisp e o "dynamic environment argument" em Kernel), mas pode funcionar. Outra solução é fazer def (o comando de definição de função) introduzir uma função local return no escopo do corpo da função, i.e., cada função vê um return diferente, mas a princípio eu não pretendia nem introduzir funções nomeadas locais (more on that later).

Também dá para simplesmente tratar def, return e companhia como special operators e era isso. Eu não queria introduzir nenhum special operator na linguagem, mas talvez isso não seja muito prático. Preciso pensar melhor sobre isso. (No fim das contas, return, break e continue trabalham com escopo léxico, enquanto exceções e unwind-protect trabalham com escopo dinâmico, então a "óbvia" unificação dos conceitos não é tão direta assim.)

Funções locais

A princípio o filosoficamente correto seria que definições de função tivessem escopo léxico, assim como as variáveis. Porém, me parece que coisas do tipo:

if {whatever} {
    def foo {
        ...
    }
}

que define uma função global ou não dependendo de uma condição, são comuns em scripts e bashrcs da vida. Daria para introduzir comandos separados para definir funções locais e globais, mas realmente não vejo muita utilidade para funções locais (além de blocos anônimos) em um shell. Se você discorda, por favor se manifeste.

(Por um lado dá para argumentar que se você realmente precisar de uma função local, pode declarar uma variável local e atribuir um bloco a ela. Por outro lado, há a diferença de que o return dentro de um bloco retorna da função externa, não do bloco. Essa questão do return não vai deixar de me assombrar tão cedo.)

Sintaxe

O uso de chaves para delimitar funções conflita com o uso de chaves em bash, que expande coisas como touch {1,2,3}.txt para touch 1.txt 2.txt 3.txt, bem como coisas como {01..99} para 01 02 ... 99. Uma solução para evitar a ambiguidade é, ao encontrar um {, continuar lendo até o primeiro espaço ou }, e se houver uma , ou .. não-escapado na string lida, considerar como um brace expansion, caso contrário como um bloco. Eu detesto esses look-aheads em parsing, mas talvez seja o caminho a seguir. (O próprio bash já faz alguma distinção contextual com relação às chaves, tratando chaves em comandos como cmd arg1 {arg2 arg3 como caracteres literais, mas em bash o parsing se dá em múltiplos passos, em que primeiro ocorre word splitting e depois brace expansion, o que torna esse tipo de coisa relativamente simples. No caso de blocos, não dá para realizar word splitting primeiro porque o bloco é mais do que só uma seqüência de "words" comuns.) Outra solução é mudar a sintaxe do brace expansion, que sequer é parte do sh to begin with (é uma extensão do bash). Discutiremos alternativas quando falarmos de arrays, em um post futuro.

Returning and replying

Comandos no Unix possuem duas formas primárias de retornar informação para o chamador:

Queremos um mecanismo que permita retornar quaisquer valores, inclusive dados estruturados como listas e blocos. Eu vejo algumas possibilidades:

  1. Estender o conceito de exit status para permitir quaisquer valores, não apenas inteiros entre 0 e 255. O problema com essa abordagem é conciliá-la com o conceito de verdadeiro e falso convencional do (ba)sh: quando meu valor de retorno é um dado arbitrário, eu provavelmente quero que a maioria dos valores sejam tratados como verdadeiro, e coisas como 0, a string vazia, a lista vazia, etc., sejam tratados como falso.
  2. Estender o conceito de stdout para permitir enviar valores arbitrários, não apenas bytes. Isso é uma idéia muito legal, e abre caminho para a implementação de um "pipeline de objetos", mas envolveria uma certa mandinga para tratar a stdout comum do Unix e a stdout de objetos transparentemente. Também tem a vantagem de que se a saída não é capturada, ela é impressa para o terminal, o que faz sentido em modo interativo. Por outro lado, provavelmente muitas vezes queremos rodar um comando apenas pelos side-effects e descartar a saída, e ficar redirecionando para /dev/null every now and then pode ser inconveniente (embora seja possível inventar uma sintaxe abreviada para isso). Além disso, isso impede que uma função cujo valor de retorno esteja sendo capturado possa imprimir normalmente para a stdout.
  3. Criar um novo mecanismo de retorno independente dos dois anteriores. Essa é a solução mais straightforward, e por enquanto é a minha working hypothesis, mas tem a desvantagem de criar um conceito extra. Para diferenciar o retorno de um valor do retorno de um exit status convencional, eu adotei a palavra reply ao invés de return (que continua existindo com seu significado convencional).

A sintaxe para chamar uma função e capturar o valor de retorno por enquanto é $[comando], pelo simples fato de que ela não está sendo usada para mais nada (em bash ela é uma sintaxe deprecated para avaliação aritmética, que hoje em dia se escreve $((expressão))), e, pode-se argumentar, porque lembra a função dos colchetes em Tcl. Eu me pergunto se ${comando} não seria uma escolha melhor, pois tem mais cara de "executa este bloco e pega o valor", mas essa sintaxe é usada em (ba)sh para delimitar nomes de variável (e.g., echo "Eu sabia essa com ${fruta}s), e não sei se é uma boa mudar isso.

Uma questão é se o reply de fato retorna da função, ou só "emite" o valor de retorno. Se o mecanismo de retorno escolhido for o (1) ou o (3), faz mais sentido retornar e sair da função, mas se a escolha for o (2), faz mais sentido emitir o valor, como se fosse um print, e seguir a execução, até porque seria possível imprimir múltiplos valores, no caso do pipeline de objetos (e aí fica a questão de como $[...] se comporta se o comando emite múltiplos valores).

Awey?

Por hoje ficamos por aqui. Como sempre, feedback é muito bem-vindo.

11 comentários

Blueprints for a shell, parte 0: Visão geral

2015-03-10 22:32 -0300. Tags: comp, prog, shell, pldesign, lash

Sim, meus caros, o mui lendário e prometido shell que eu estou há anos dizendo que quero escrever está mais perto do que nunca de talvez ser escrito. Isso se deve a uma decisão de vida curiosa que me deixou com mais tempo para projetos pessoais, pelo menos por enquanto.

A questão é: tem um trilhão de decisões de design que eu preciso tomar e que eu gostaria de pensar bem sobre e discutir antes de começar a implementar. Assim, me pareceu uma boa escrever sobre elas aqui para me ajudar a organizar as idéias e coletar comentários, sugestões e opiniões. A idéia original era escrever um único post com tudo, mas eu comecei a fazer isso ontem e me dei conta de que ele ia acabar ficando gigante. Então, o plano agora é escrever uma série de posts. Neste aqui, apresentarei as idéias básicas do novo shell, e nos próximos pretendo entrar nos detalhes de features mais específicas, tais como tipos de dados, quoting, closures, estruturas de controle, módulos, escopo de variáveis e afins.

Por que um novo shell?

Eu já escrevi um post (gigante) sobre o assunto antes, mas basicamente: o shell é uma péssima linguagem de programação. Embora o bash tenha adquirido inúmeras features ao longo dos anos, coisas que se esperam de qualquer linguagem de programação que se leve a sério, tais como dados estruturados de primeira classe e a possibilidade de retornar valores de funções sem criar um subprocesso, não existem até hoje. Acho que existe um círculo vicioso na evolução dos shells: shells não são vistos como linguagens de programação "de verdade" por seus usuários por terem programabilidade pobre, e os desenvolvedores de shells não melhoram a programabilidade do shell porque não há demanda dos usuários. A premissa do novo projeto é romper com essa idéia e tornar o shell uma linguagem "decente" como Perl, Python ou Ruby, sem entretanto perder as características que tornam um shell conveniente, i.e., a facilidade de chamar e combinar programas de linha de comando e de utilizá-lo como uma interface interativa para o sistema operacional.

Objetivos gerais

Eis uma lista das idéias básicas que hão de guiar o desenvolvimento desse novo shell.

A sintaxe de uso interativo freqüente deverá permanecer largamente igual à do (ba)sh. Coisas como redirecionamentos simples (>, >>, <), pipes, globbing (*.txt, /dev/tty[1-8]), tilde expansion (cd ~/Desktop), etc., manterão a mesma sintaxe. A sintaxe dessas coisas é tradicional demais (e familiar até a usuários de ambientes não-Unix), então me parece melhor mantê-la igual, mesmo que isso limite as escolhas sintáticas para outras funcionalidades do shell.

Dito isto, compatibilidade com (ba)sh não é um objetivo do shell. A manutenção da sintaxe das funções mais comuns é mais uma questão de compatibilidade com os usuários de shell do que com os shells propriamente.

Uma das features mais importantes do shell novo é o suporte a dados estruturados de primeira classe. Isto é, arrays e dicionários podem ser armazenados em variáveis, dentro de outros arrays e dicionários, passados como argumento para funções, retornados por funções, etc. Isso implica a adição de um mecanismo para retorno de valores complexos por funções, bem como uma sintaxe para chamar uma função e capturar seu valor de retorno, sem criar um subshell para isso (diferente do $(...) do (ba)sh).

Outra feature importante é o suporte a closures, ou blocos de código de primeira classe. Isso permite a substituição de diversas estruturas de controle que têm sintaxe especial no (ba)sh (if, for, while, etc.) por comandos simples que recebem blocos de código como argumento, e também permite que novas estruturas de controle sejam definidas pelo usuário.

O suporte a closures e a funções com valores de retorno complexos nos possibilita fazer uma grande limpeza na sintaxe do shell, substituindo certos elementos de sintaxe questionável (e.g., ${var,,*}) por equivalentes mais legíveis (e.g., $[lowercase $var]). A idéia é inicialmente ter o mínimo de sintaxe especial. Porém, sintaxe minimalista não é necessariamente um princípio sagrado, e se for observado que algumas operações são freqüentes o suficiente para justificar uma sintaxe especial, tal sintaxe pode vir a ser acrescentada ao shell.

O shell deve facilitar a escrita de scripts robustos. Em (ba)sh é muito fácil escrever um script que aparentemente funciona corretamente, mas falha diante de nomes de arquivo com espaços ou quebras de linha, ou comandos que usam * ou ? com seu sentido literal e funcionam 99% do tempo, mas falham misteriosamente ocasionalmente, porque coisas como *~ são mantidas intactas pelo (ba)sh quando não há nenhum arquivo que case com o padrão, o que faz com que o comando funcione ou não dependendo do conteúdo do diretório atual, ou porque o (ba)sh expande caracteres epeciais em situações inesperadas. O shell deve ter um comportamento consistente, fácil de "reason about", sem dependências mágicas de contexto e do ambiente do usuário.

Uma preocupação secundária mas importante é que o shell deve ser razoavelmente rápido. Não necessariamente rápido como uma chita, mas idealmente com uma performance equiparável à de Python ou Ruby. (Isso não precisa ser uma preocupação inicialmente, mas é bom mantê-la em mente durante o design da linguagem.) O bash é absurdamente lento, e shell scripts em geral tendem a ser lentos por terem que usar comandos externos para diversas coisas que em outras linguagens seriam builtins ou bibliotecas. O que nos leva ao próximo item...

O shell deve ter suporte a bibliotecas, módulos ou algo do tipo para reuso de código. Idealmente, também deve ser possível escrever bibliotecas/módulos compilados que possam ser utilizados por scripts. Isso permite a adição de features sem engordar o shell. Deve ser fácil distribuir e instalar bibliotecas para o shell. Idealmente, também deve ser fácil determinar e instalar (semi-)automaticamente as bibliotecas das quais um script depende. Deve ser possível isolar namespaces para evitar conflitos de nomes.

Finalmente, features interativas, como edição de comandos e histórico, não devem ser parte do core do shell. Com suporte a bibliotecas/módulos, não há por que colocar essas funcionalidades no binário principal e carregá-las ao rodar scripts, que não precisam delas.

Remarks on syntax

O fato de que o shell deve ser conveninete de usar interativamente, e de que desejamos manter um mínimo de "compatibilidade de usuário" com a sintaxe tradicional do sh, impõe certas restrições nas escolhas sintáticas do shell. Por exemplo, em uso interativo, strings literais são mais freqüentes do que variáveis, então faz sentido que strings não exijam aspas e variáveis sejam introduzidas por um símbolo especial ($). < e > possuem significados convencionais, então embora às vezes seja muito tentador utilizá-los como delimitadores para alguma outra coisa, o melhor é deixá-los em paz.

Essas restrições fazem com que seja difícil escolher uma sintaxe para certas features que seja "ergonômica" para programar e ao mesmo tempo não interfira e se encaixe direito com o resto do shell. Até hoje eu não encontrei uma sintaxe para capturar o resultado de uma função que me agrade totalmente, por exemplo.

Dito isso, embora eu seja da opinião de que "syntax matters", eu cheguei à conclusão de que, pelo menos inicialmente, considerações sintáticas não são tão importantes assim, já que em geral a sintaxe pode ser alterada mais adiante sem muito impacto no resto do projeto (pelo menos enquanto ele não for oficialmente released e não tivermos que lidar com esse negócio de "usuários"). Assim, vou aceitar por ora uma certa dose de bizarrice sintática quando não houver uma escolha obviamente melhor, deixando aberta a possibilidade de mudanças futuras.

Um outro fator ao qual eu pretendo dar um peso importante ao definir a sintaxe é o que podemos chamar de dificuldade de interpretação incorreta. Por exemplo, eu costumava não ir muito com a cara do uso de my em Perl para declarar variáveis locais, mas ele tem o mérito de deixar claro (para mim, pelo menos) que trata-se de uma declaração de variável (e não uma atribuição a variável já existente), e que o escopo dela é o bloco em que se encontra (e não a função ou o módulo ou whatever). let também é relativamente claro, mas let é um comando que faz algo diferente em bash (avaliação de expressões aritméticas e atribuição (não declaração)), então talvez seja melhor evitar essa palavra (mas ainda estou meio em dúvida).

Um contra-exemplo é a sintaxe para declaração de variáveis de ambiente temporárias: em um comando como

LC_ALL=C find /home | grep '[^A-Za-z0-9]'

o LC_ALL=C vale só para o find, ou para o grep também? (Resposta: só para o find.) Em um comando como

foo=42 echo $foo

$foo está no escopo da definição ou não? (Resposta: não.) No geral, acho preferível escolher uma sintaxe que não deixe dúvida de qual é a interpretação correta. Similarmente, se alguma distinção é importante, pode ser melhor obrigar o usuário a especificá-la ao invés de usar um default que freqüentemente pode não ser o que o usuário quer, ou que pode induzir ao erro alguém lendo o código escrito por outra pessoa. Por exemplo, eu não pretendo ter uma função len para strings no shell, mas sim funções como bytelen (número de bytes), charlen (número de codepoints Unicode) e charwidth (largura do texto na tela), exigindo que o programador seja específico quanto a o que quer dizer com "comprimento" da string. (Esses nomes ainda são meio questionáveis, pois o encoding das strings (que supostamente é UTF-8) fica implícito, mas ainda não pensei com calma sobre o assunto.)

A teaser

Embora por enquanto nada esteja muito bem definido, para tornar as coisas um pouco mais concretas, eis um exemplo da cara que eu imagino que a tal linguagem vai ter:

# Função que retorna um dicionário contendo a quantidade de usuários
# que usam cada shell.

def count_shell_users {
    my counts = %()
    each_line </etc/passwd {|line|
        my (user pass uid gid name home shell) = $[split $line ":"]
        counts{$shell} = $(( $[or $counts{$shell} 0] + 1 ))
        # (A sintaxe da linha acima provavelmente não é definitiva.)
    }
    reply $counts
}

# Função que retorna todos os elementos de uma lista que satisfaçam um predicado.

def filter {|list predicate|
    my result = ()
    each $list {|item|
        if {$predicate $item} {
            push $result $item
        }
    }
    reply $result
}

# Exemplo de uso.
my dirs = $[filter (/etc/*) {|x| isdir $x}]

Por hoje é só

Por hoje ficamos por aqui. Nos próximos episódios trataremos de tópicos mais específicos. Perguntas, sugestões, opiniões, comentários, tanto sobre os tópicos abordados quanto sobre outras coisas que você gostaria de ver (ou não) num shell, são muito bem-vindos.

(By the way, não havendo conflito com nenhum projeto ativo, o shell a princípio deverá se chamar lash (lambda shell).)

8 comentários

Oi, UOL, e a engenharia social fdp

2015-01-21 23:39 -0200. Tags: life, worldly, treta

Meus amigos, falcatrua é uma arte[2], e é tanta que eu resolvi criar uma tag treta só para catalogá-la.

Para quem vive falando de privacidade e cuidado com dados pessoais, eu consegui ser bem trouxa hoje caindo no golpe do UOL. E eu me dei conta de que é possível manipular as pessoas por telefone de uma maneira que não é possível (pelo menos não com a mesma facilidade) pela Internet. Relato aqui as falcatruas e o que eu aprendi com elas.

Primeiro ato

Depois de ter que cancelar a minha linha da GVT por falta de disponibilidade no meu endereço novo (que é uma história à parte, cuja moral pode ser resumida em reclame com a Anatel e não aceite meias soluções), eu contratei uma linha telefônica e banda larga da Oi. No dia em que eu fiz o pedido, a atendente me informou que o UOL me ligaria para oferecer o provedor e eu poderia dispensar se não quisesse. Alguns dias depois, uma vendedora do UOL ligou para o meu celular (sim, a Oi, não contente em apenas divulgar o número da linha nova, passou meu celular também para o UOL) e eu informei que não tinha interesse. So far, so good.

Hoje o técnico da Oi veio ligar a linha. Uma ou duas horas depois, eu recebi uma ligação no número novo. A pessoa informou seu nome e disse (não lembro exatamente os detalhes) que ia fazer a confirmação de alguns dados para a ligação da Internet e passar algumas informações. Em nenhum momento ela afirmou ser do UOL. Depois de me passar um telefone da central de atendimento, me pedir uns dados, informar uma senha e meia dúzia de outras informações, a mulher me pediu, com uma série de rodeios e palavras elegantes, um número de cartão de crédito. Informou ainda que a cobrança viria pela conta do telefone, e o número não precisava ser de um cartão em uso, podia ser um antigo, era só para ter uma "referência". Depois de enrolar e perguntar uma ou duas vezes para que mesmo ela precisava do cartão, eu disse que não tinha o cartão à mão para informar o número. Ela então me perguntou se eu tinha uma conta bancária para informar, que era um procedimento de segurança da empresa pedir essa informação, que precisava fazer isso para ativar o serviço de Internet, etc. No fim eu forneci o número da conta, julgando que essa informação não era tão crítica por si só. A pessoa confirmou todos os dados de novo e desligou.

Depois de ponderar sobre a estupidez que eu tinha acabado de fazer, resolvi tentar descobrir quem tinha me aplicado o golpe, procurando na Internet pela única informação identificável que a pessoa me forneceu: o telefone da central de atendimento fornecido: 0800 771 7774. Com isso, descobri não só que o número é do UOL, como também este post no Facebook:

“ALERTA A TODAS AS PESSOAS QUE ASSINARAM A OI VELOX”

Assim que assinamos a Oi Velox, recebemos ligações de pessoas q dizem ser da Oi, se apresentam dizendo que fazem parte da oi Velox, (SÃO EXTREMAMENTE CONVINCENTES ), mas são da empresa UOL, que por meio de bandidagem fazem com que os clientes da Oi Velox assinem esse provedor, mas tem um jeito de saber quando são eles quem ligam, eles confirmam seus dados e no final pedem CARTÃO DE CREDITO, pra cobrar pelo acesso, CUIDADO, sempre q alguém ligar na sua casa falando q é da Oi Velox, “DESCONFIEM”, e quem caiu nessa empresa q na verdade é uma quadrilha, liguem pra UOL pelos números 4003-2002 (regiões metropolitanas) e 0800 771 7774 (demais cidades). Não é necessário pagar por provedor de acesso, a própria Oi Velox pelo numero 103-31 já te dão o acesso a internet. e por favor, liguem na Oi central de Reclamações e denunciem isso, pq já passou da hora dessa pouca vergonha acabar... COMPARTILHEM, PRECISAMOS UNIR CONTRA ESSES BANDIDOS, depois de reclamarem na Oi, liguem tb pra ANATEL E DENUNCIEM PELO TEL: 1331.

UNIDOS FAZEMOS DIFERENÇA....

entre inúmeros outros resultados falando da falcatrua. Em seguida, eu liguei para o atendimento da Oi, perguntei sobre a instalação do ADSL, agendei a visita técnica (hoje ligaram só a linha telefônica, sem ADSL), e informei que não queria nenhum provedor e perguntei se aparecia no sistema deles o UOL como provedor. A atendente informou que a contratação do provedor é independente da Oi e eles não têm como consultar essa informação.

A seguir, liguei para o UOL para saber se eles tinham feito a contratação do serviço e dizendo que eu não tinha a intenção de contratar nenhum serviço do UOL e não tinha autorizado nenhuma contratação. Depois de me perguntar e eu informar detalhes sobre a ligação que eu recebi, e me perguntar que empresa tinha me ligado (eu informei que a pessoa não tinha informado, mas passou um 0800 que era do UOL), a atendente informou então que fez um pedido de "cancelamento". Eu então perguntei se seria emitida alguma cobrança. A atendente disse que "já tinha me informado que fez o cancelamento". Eu disse que "mas se o serviço foi cancelado, é porque um dia ele existiu; se um dia ele existiu, pode ser cobrada alguma coisa; eu quero ter certeza de que o UOL não vai me cobrar nada". A mulher ainda me "xingou" por ter fornecido dados sem saber quem tinha ligado (cara de pau também é uma arte), aparentemente querendo implicar que o UOL não tinha nada que ver com isso (mas ela acabou de registrar um pedido de cancelamento! Cancelamento do quê, se o UOL não tinha nada que ver com isso?). "O UOL não vai me cobrar nada então?", eu perguntei, e ela respondeu "já disse que não" (embora esse fosse o primeiro momento em que ela disse que não). Eu agradeci e desliguei. Não sei se o UOL grava as ligações, mas se grava, pelo menos ficou um registro explícito da pessoa dizendo que nenhuma cobrança seria emitida.

Depois disso eu fui no banco ver se tinha como bloquear a adição de faturas em débito automático. A pessoa do banco disse que não tem, só tem como bloquear convênios individuais, depois que eles foram adicionados (coisa que eu já sabia que dá para fazer pelo próprio terminal do banco); que se for adicionado algum débito automático ele provavelmente aparecerá nos "lançamentos futuros" do extrato, e aí eu posso cancelar; e que se eu só vir que entrou a cobrança depois de ela já ter sido debitada, também é possível cancelar e reaver o dinheiro, mas demora alguns dias. Também aproveitei para bloquear o crédito do meu cartão, que eu não uso anyway.

Segundo ato

À tarde, eu recebi uma ligação do suporte técnico da Oi. A atendente me informou que se me ligassem pedindo informações bancárias eu não as devia passar, pois o UOL e o Terra estavam ligando dizendo que são da Oi e pedindo essas informações, mas a Oi nunca pede esses dados e manda qualquer cobrança pela conta do telefone. [Eu não sei se a Oi deixou as coisas saírem do controle e agora está tentando remendar a situação, ou se eles só querem passar a imagem de que estão fazendo alguma coisa a respeito desse problema com os provedores para evitar que a Anatel coma o fígado deles, mas na verdade pretendem manter a situação como está. Anyway.]

Depois (ou antes, não tenho mais certeza), a atendente confirmou alguns dados pessoais, e perguntou se a instalação do ADSL já estava agendada (por puro acaso eu já tinha feito isso pela manhã ligando para a Oi, vide acima). Tendo feito tudo isso e informado que qualquer cobrança viria pela conta, informou ainda que seria cobrado apenas o valor do plano mais R$ 9,90 do suporte técnico, que o suporte técnico não tinha fidelidade e podia ser cancelado quando eu quisesse, e que ela ia me passar para a ouvidoria apenas para confirmar a contratação do serviço, que eu só teria que informar os três primeiros dígitos do meu CPF, e que qualquer dúvida que eu tivesse eu devia perguntar antes de ser transferido para a ouvidoria, pois lá eles não responderiam dúvidas, apenas confirmariam a contratação. "Essa confirmação é em relação só ao suporte?", eu perguntei. "É para a liberação do serviço", ela respondeu, e disse mais algumas coisas que não lembro mais. Do jeito que ela falou, deu a entender que se tratava da liberação do ADSL. Felizmente a Anatel deve ter andado caindo de pau em cima da Oi nos últimos tempos, que é o provável motivo de haver essa confirmação em separado com a ouvidoria. "Ok, pode transferir". O cara da ouvidoria confirmou alguns dados (again), me pediu os três primeiros dígitos do CPF, e me perguntou se eu "confirmava a contratação do serviço de suporte técnico da Oi por R$ 9,90 por mês, que é opcional e não afeta em nada o uso dos outros serviços da Oi". Eu disse que se o serviço era opcional, então eu não tinha interesse. O cidadão agradeceu e desligou.

Uns tantos minutos depois, a atendente anterior me ligou de novo, dizendo que eu não tinha confirmado e perguntando o que tinha acontecido. Eu informei que o cara da ouvidoria tinha dito que o serviço era opcional e então eu disse que não tinha interesse. Ela reclamou que "mas eu informei o senhor que era necessário confimar o serviço, etc.". Eu disse que tinha entendido que era a liberação do ADSL que eu tinha que confirmar. Ela me explicou em linguagem ambígua que era a liberação do serviço de suporte técnico, que se eu precisasse de suporte eu tinha que ter contratado, etc. Eu disse "bom, se um dia eu precisar de suporte eu contrato então". Ela disse "não é bem assim", que se eu não contratasse naquele momento eu renunciava ao serviço e não poderia contratar depois. Eu disse que tudo bem e ela desligou.

Análise

Infelizmente eu não fiz gravações dessas conversas, porque elas seriam um ótimo material de estudo (e depois desses episódios eu estou pensando seriamente em arranjar um aparelho de telefone capaz de gravar ligações). O ponto interessante dessa história toda é que evidentemente há técnicas sendo usadas intencionalmente para ludibriar as pessoas:

Honestamente eu não sei se os tons de voz foram calculados para maximizar o poder de manipulação, ou se essas pessoas estavam fazendo isso naturalmente. O fato é que esses vendedores provavelmente recebem treinamento extra de manipulação, e convém ficar com um pé atrás e atento a essas artimanhas (que são extremamente sutis e fáceis de não notar quando não se está procurando por elas) ao tratar com vendedores e outras fontes de manipulação.

Umas lições de vida, portanto

Uma política "óbvia" que eu hei de ter de agora em diante é (obviamente) não fornecer dados bancários, de cobrança ou de outra forma sensíveis em qualquer ligação que eu não tenha iniciado. (Você intencionalmente ligar para a operadora para pôr a conta em débito automático, por exemplo, é uma situação em que é legítimo passar esses dados por telefone.) Eu digo "óbvio" entre aspas porque até ontem se alguém me perguntasse se devia passar dados bancários para alguém que lhe ligou eu diria "óbvio que não, né", mas mesmo assim eu fiz isso hoje, porque na situação com o nervosismo e a dúvida e a aparência legítima da coisa toda é mais fácil se deixar levar do que parece. "Será que não é a Oi mesmo? Será que eu não preciso fazer isso para virem ligar o bendito ADSL? Como eu vou dizer pro atendente 'não quero passar esses dados porque isso pode ser um golpe', e se for o atendente de verdade?" Por isso é importante ter uma política explícita de não fornecer esses dados de forma alguma se não foi você que iniciou a ligação e não está falando com um conhecido (e mesmo assim). Assim como o vendedor pode lhe dizer que "é uma política de segurança pedir essa informação", você também pode dizer que é uma política de segurança sua não dar essa informação. Se você quiser justificar (e você não tem que), pode dizer que há muito golpe hoje em dia, e você prefere não correr o risco. Se você ficar com receio de ofender o atendente legítimo (e você não tem que), ou ele disser "mas eu sou o atendente legítimo", você pode bancar o paranóico e dizer que telefone é um meio inseguro, muito fácil de interceptar (é só subir num poste ou abrir um armário e escolher os fiozinhos certos), e você prefere não fornecer essa informação por telefone porque alguém pode capturá-la e usar para algum fim ilegítimo. Pronto. E da próxima vez me mostre o certificado SSL.

A outra lição importante a ter sempre em mente nessas e noutras situações é que provavelmente estão tentando te manipular, existem métodos eficazes para isso, e você deve estar atento a eles para não se deixar levar. (Especialmente quando um atendente está tentando nos constranger ou compelir a fazer algo, lembrar disso acalma e ajuda a pensar direito.)

E assim acaba o conto. (A história da GVT, que é mais de incompetência do que de falcatrua, fica para outro post.)

8 comentários

Gambiarras LaTeXísticas

2014-12-18 00:25 -0200. Tags: comp, latex, mundane

Neste post apresento duas pequenas gambiarras que eu descobri durante a confecção do meu PEP e TI.

Inserindo recortes de PDFs como figuras

O comando \includegraphics do pacote graphicx permite inserir imagens em diversos formatos, incluindo PDF. (Para usar o comando, inclua \usepackage{graphicx} no começo do seu documento LaTeX. O graphicx vem no pacote texlive-latex-base no Debian.) Por ser um formato de imagem vetorial, a qualidade do resultado normalmente é melhor do que incluir um PNG ou similar. Além disso, o \includegraphics permite inserir "recortes" do PDF, usando as opções page, clip e trim. Por exemplo, você pode usar algo como o seguinte trecho para recortar uma figura de um PDF existente e inseri-la como uma figura no documento atual:

\begin{figure}
\centering
\includegraphics[page=4,clip=true,trim=4cm 20.5cm 4cm 5cm]{arquivofeliz.pdf}
\caption{Gráfico roubado de \cite{fulano-et-al-2014}}
\label{graph1}
\end{figure}

page especifica a página do PDF, clip=true habilita o recorte, e trim consiste de quatro tamanhos especificando quanto se deseja cortar fora da esquerda, de baixo, da direita, e de cima da página, nessa ordem (i.e., em sentido anti-horário começando da esquerda). Acertar os valores de trim exige um pouco de tentativa e erro. Além disso, é possível especificar parâmetros como width=15cm para redimensionar a figura. Mais informações aqui.

Isso é útil para incluir figuras de outros documentos, ou para exportar gráficos, tabelas e afins de outros programas para o LaTeX, já que muitos programas são capazes de imprimir/exportar para PDF. Você pode usar isso para importar tabelas do Open/LibreOffice Calc, por exemplo.

Citation needed

Às vezes as abreviações/reformatações de nomes de autor no BibTeX falham miseravelmente. Por exemplo, se você tiver no seu arquivo .bib algo como:

@ELECTRONIC{rust,
  title = {The Rust Reference},
  author = {The Rust Project Developers},
  year = {2014},
  howpublished = {\url{http://doc.rust-lang.org/reference.html}},
  note = {Accessed in December 2014}
}

no estilo de bibliografia abnt-ufrgs (e, presumivelmente, no abntex também), a referência fica como "DEVELOPERS, T. R. P". Se você trocar por author = {Rust Project Developers, The}, a referência fica como "RUST PROJECT DEVELOPERS, T.". Provavelmente existe um jeito bonito e elegante de contornar esse problema, mas a solução suja que eu encontrei foi que se o nome do autor for colocado entre (mais um par de) chaves, ele é usado literalmente na referência (mantendo maiúsculas e minúsculas inclusive). Assim, podemos usar:

@ELECTRONIC{rust,
  title = {The Rust Reference},
  author = {{RUST PROJECT DEVELOPERS, The}},
  year = {2014},
  howpublished = {\url{http://doc.rust-lang.org/reference.html}},
  note = {Accessed in December 2014}
}

para obter o resultado desejado. Só tem um problema: agora as referências inline com \citep{rust} e companhia aparecem como "(RUST PROJECT DEVELOPERS, The, 2014)". A solução suja que eu encontrei foi definir um alias de citação:

\defcitealias{rust}{RUST, 2014}

e citar usando \citepalias ao invés de \citep (ou \citetalias para não incluir os parênteses (mas os detalhes dependem do bibliography style usado, acho)).

(Disclaimer: talvez as normas da ABNT realmente requeiram "RUST PROJECT DEVELOPERS" ao invés de meramente "RUST" na citação, mas não fui atrás para descobrir. De qualquer forma, imagino que o "The" não deva ser incluso.)

Happy kludging.

1 comentário

Yeah, I code-switch heavily

2014-11-02 02:31 -0200. Tags: lang, life, ramble

Semana passada eu resolvi ir no Verda Kafo (evento esperantista que ocorre no último sábado de todo mês em Porto Alegre), depois de alguns meses de sumiço. Um verdkafano perguntou como ia o meu mestrado, e o Marcus mencionou que o meu blog tinha a resposta. A pedidos, eu passei a URL para ele e mais um dos participantes. Foi só algumas horas depois disso que eu pensei "bá, vão me encher o saco no próximo Verda Kafo por causa das frases em inglês strewn in no meio dos textos", mas aí já era tarde.

Esse fenômeno de alternar entre línguas em um mesmo diálogo ou em uma mesma frase é denominado code-switching.* O artigo da Wikipédia menciona uma porção de explicações sociológicas de por que as pessoas code-switcham. No meu caso, entretanto, for most part, acho que nenhuma das explicações apresentadas se encaixa. Eu simplesmente acho mais fácil dizer algumas coisas em inglês, às vezes porque a sintaxe do termo em inglês é diferente, às vezes sem nenhum motivo aparente. A grande maioria das coisas que eu leio são em inglês, e eu passo boa parte do meu tempo lendo, então acho que não é de admirar. Em tempos de outrora, quando eu estava aprendendo esperanto e o usava com mais freqüência, era bastante comum eu achar mais fácil dizer algumas coisas em esperanto do que em português, primariamente graças ao sistema de composição e derivação supimpa (eu ainda uso "X-ilo" ocasionalmente, onde X é uma palavra em português ou em esperanto, para me referir ao "utensílio de fazer X"), mas às vezes também porque a minha cuca queria dizer alguma coisa com uma estrutura sintática e o português exige outra. Um exemplo "clássico" disso com o inglês são frases como she was named after a tree, que eu nem tenho certeza de como dizer em português ("ela foi nomeada segundo uma árvore" doesn't quite cut it (como se diz "doesn't quite cut it" em português?)).

Poder-se-ia alegar que isso representa a decadência do português e o efeito do imperialismo estadunidense. Eu não sei. Em primeiro lugar, a identidade da língua portuguesa está bem saudável, já que são poucos os falantes de português que fazem code-switching. Em segundo lugar, assim como eu acho mais fácil dizer certas coisas em inglês, há uma porção de outras coisas que eu acho mais fácil dizer em português. Acontece simplesmente que, nas situações em que meus interlocutores falam tanto português quanto inglês, a conversa se dá primariamente em português (evidentemente), então é raro eu ter a oportunidade de falar inglês com frases em português strewn in. (Cabe notar que eu só atravesso termos em inglês quando eu sei que o interlocutor os há de entender, já que, imaginem vocês, comunicação exige entendimento entre as partes. Porém, em alguns casos eu tenho que fazer um esforço extra para dizer certas coisas em português ao invés de falar da maneira que me é mais confortável.)

Por fim, enquanto eu estava ideando este post, eu pensei comigo mesmo: "Seriously, tu tá te justificando pela maneira como tu escreve no teu próprio blog? Que diabos é isso, um blog de gente se explicando?" Não era nem para eu ter que escrever isso (de fato, eu não tenho que escrever isso), mas enfim.

_____

* Segundo o artigo, o uso de múltiplas línguas na escrita é chamado de linguagem macarrônica, mas aparentemente o termo é mais usado para descrever certas formas literárias em que a mistura tem algum propósito especial, freqüentemente humorístico. No caso aqui do blog, entretanto, geralmente o meu uso de inglês atravessado no meio do texto simplesmente reflete a maneira como eu falo quando sei que o interlocutor entende ambas as línguas. No geral, eu tendo a fazer isso mais nos posts mais "pessoais" e menos nos posts mais informativos. Acho.

7 comentários

Modelos LaTeX do INF/UFRGS

2014-10-22 17:06 -0200. Tags: latex, academia, mestrado

Update (23/03/2015): Utilize a versão mais atual do pacote iiufrgs.

(Os modelos que eu disponibilizava aqui are no more.)

3 comentários

State of the life

2014-10-17 01:37 -0300. Tags: life, mind, academia, mestrado, home

As pessoas gostam de dizer que o tempo passa rápido. Eu nunca concordei com essa afirmação; para mim, um ano sempre levou um ano inteiro para passar. Mas esse ano em particular está se puxando em termos de neverendingness. Para futuras consultas, e para quem tiver interesse, deixo aqui um registro simplificado dos fatos.

Em janeiro deste ano, meu pai se mudou para um apartamento em Porto Alegre, e eu, que até então morava em uma casa alugada de solidez questionável, me mudei para a recém desocupada casa. Após uma série de acidentes vivenciosos, cuja enarração está fora do escopo deste livro, encontrei-me por volta de julho dividindo a casa com meu pai e minha irmã, compartilhando com esta última um quarto de 2×2,5m, e tendo que cuidar da mesma no período da tarde. Uma vez que essa obrigação conflitava com a necessidade de comparecer à UFRGS para a bolsa do mestrado e a essas alturas eu já estava perdendo as estribeiras, em uma última tentativa de salvar um pouco da minha sanidade mental, falei com meus orientadores sobre a possibilidade de trancar a matrícula. No fim das contas, ficou combinado que eu poderia continuar realizando as atividades do mestrado remotamente (já terminei todos os créditos necessários (que a partir deste ano são 12, e não 24), e agora só me restam o Trabalho Individual e a dissertação e atividades relacionadas), eu fiquei mui faceiro, e ficou por isso mesmo.

Evidentemente, isso não foi nada produtivo, dada a falta de sossego e de coisas como uma mesa decente e isolamento e o fato de eu passar constantemente estressado ou cansado ou deprimido. Nesse meio tempo eu descobri que é possível comprar casas super-barato nas verdejantes terras de Viamãoheimr se o camarada não se preocupar com trivialidades como escritura e terreno em área verde, e comecei a catar locais para me mudar para no ano que vem. Depois de umas três ou quatro visitas a locais de solidez ainda mais questionável do que a anteriormente citada casa e em localizações pra lá de interessantes, eu larguei de mão essa idéia e decidi esperar até o fim do ano (quando eu me livro da obrigação de cuidar da criança) para alugar uma casa e me mudar.

No dia 15 de setembro, quando eu estava considerando trancar a matrícula pela (n+1)-ésima vez, eu me dei conta de que se eu alugasse uma casa suficientemente perto eu não precisaria esperar até o fim do ano para me mudar; eu podia me mudar agora e só ir para a outra casa à tarde. Até então eu estava tão fixado na idéia de comprar uma casa que isso nem me passou pela cabeça. Catei casa para alugar nas redondezas e encontrei uma elegante peça de 3×5m mais um banheiro para alugar por meia pataca. A saga para conseguir alugar esse bendito imóvel é uma história à parte que eu hei de postar em algum momento para a elucidação de todos os seres sencientes. Suffice it to say que sexta-feira da semana passada eu consegui me mudar para o lugar, e que turns out que 15m² são um tamanho bem razoável se bem utilizado e quando não se tem que dividi-lo com ninguém.

A situação está um pouco melhor desde que eu me mudei para cá. Por outro lado, até o fim do ano eu continuo tendo que brincar de babá às tardes e em outros momentos de interesse, entre outros eventos que me levam a questionar se o conceito de respeito pelo tempo dos outros existe na cabeça da população em geral ou se é alguma flutuação da minha imaginação, mas eu vou levando, pois é só até o fim do ano, e depois disso eu espero me sentir mais confortável em dizer não diante de proposições equiparáveis.

Quanto ao mestrado, by now I'm pretty sure de que eu não quero seguir a carreira acadêmica e viver de publicar papers. Aparentemente é possível ganhar uns bons trocos dando aula em instituições privadas de ensino superior sem ter que fazer pesquisa, e por enquanto este é o meu plano para depois que eu terminar o mestrado. (Para a minha definição de "uns bons trocos", pelo menos. Certamente não tanto quanto um professor titular de universidade federal, mas a carga horária é menor também. Eu não tenho a necessidade de ganhar rios de dinheiro e, contanto que eu ganhe o suficiente para levar uma vida decente e guardar uns trocados, eu prefiro ter mais tempo do que mais dinheiro. Eu me sinto um pouco desconfortável dizendo isso para as pessoas, porque é basicamente uma admissão de vadiagem, mas should it matter, se eu estiver me mantendo com meu próprio dinheiro sem pedir nada para ninguém? Enfim.) Eu tenho tido dificuldade em me motivar a fazer as atividades do mestrado, mas eu tenho levado, e o prognóstico de terminar isso tudo e deixar para trás é bastante animador.

By the way, lembram quando eu manifestei minha descrença pela idéia de que as aulas do mestrado "não são mais do mesmo"? Hell was I right. As predicted, as aulas são much the same thing (pelo menos as que eu tive). A melhor cadeira que eu fiz no semestre foi Programação Funcional Avançada, que nem era do mestrado (foi a cadeira que eu escolhi para realizar a Atividade Didática). Algoritmos e Teoria da Computação foi bacaninha também (em particular a parte de Teoria da Computação), mas nada muito diferente de uma cadeira da graduação. De resto, o semestre foi tolerável primariamente graças aos pães do Hélio. Also as predicted, a variedade de cadeiras (ou falta de) também limita as possibilidades de pegar apenas cadeiras em tópicos de interesse; aliás, os horários das cadeiras do mestrado são um tanto quanto mal-distribuídos (não há praticamente nenhuma cadeira às 8h30, por exemplo), o que aumenta a possibilidade de conflitos de horários entre cadeiras (semestre passado eu tive que escolher entre uma cadeira de programação paralela e uma de tendências em engenharia de software por conta do conflito de horários, por exemplo); soma-se a esse problema o fato de que a maioria das cadeiras é oferecida em apenas um dos semestres do ano, e que recomenda-se fortemente aos alunos cursarem todas as disciplinas no primeiro ano do mestrado. On the bright side, esse ano o PPGC resolveu reduzir o número de créditos obrigatórios do mestrado para 12, o que permite realizá-los todos no primeiro semestre.

Also on the bright side, eu consegui (depois de muitas conturbações, primariamente na minha cabeça) escolher um tema de dissertação em um assunto que me interessa. Vai dar um baita trabalho para implementar, mas pelo menos eu vou aprender coisas pessoalmente úteis para mim no processo.

Do futuro, falamos depois.

6 comentários

Main menu

Posts recentes

Tags

comp (80) life (31) prog (31) unix (29) random (23) mundane (21) lang (20) about (18) mind (15) web (13) img (11) rant (11) pldesign (10) ramble (9) privacy (8) esperanto (7) bash (7) shell (6) home (6) lash (5) conlang (5) academia (5) freedom (4) book (4) lisp (4) copyright (4) worldly (4) mestrado (4) misc (4) film (3) music (3) kbd (3) politics (3) security (3) network (2) poem (2) editor (2) physics (2) php (2) android (2) cook (2) latex (2) wrong (2) treta (2) c (2) pointless (1) kindle (1) audio (1) perl (1)

Elsewhere

Quod vide


Copyright © 2012-2015 Vítor Bujés Ubatuba 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.