DEV Community

Lucas Perez
Lucas Perez

Posted on

Não use uma variável chamada path dentro de funções quando estiver usando o zsh

🏴󠁧󠁢󠁥󠁮󠁧󠁿 English version here.

Antigamente eu criava aliases para coisas comuns, como uma sequência de comandos ou comandos com flags e opções específicas. Porém, quando ficava muito complicado, eu criava um shell script para lidar com coisas mais complexas. Aí então eu criava aliases que simplesmente chamavam esses scripts. Eu preferia isso do que por o script no meu path porque daí meus scripts podiam ter nomes descritivos e eu não teria que ficar lidando com alterar a variável PATH

Mas daí em algum momento eu percebi que eu podia simplesmente criar funções nos arquivos de configuração do meu shell (como por exemplo o .bashrc, o .zshrc etc) e daí eu teria essas funções disponíveis para mim. Além disso, usando a estratégia script+alias, quando eu executava which <comando>, eu não teria uma saída tão útil assim, pois aparecia para mim algo como comando=/caminho/para/o/script. Mas quando eu defino funções, executar which <função> me mostra a implementação dela.

Eu acho que o comportamento de which não é o mesmo em todas as distros, mas no Fedora ele faz isso.

É claro que se a implementação de tal função for muito grande, talvez mostrá-la não seja tão útil assim, mas enfim.

Você pode basicamente copiar e colar seus shell scripts em definições de funções, só tem que lembrar que muito provavelmente você vai querer trocar exit por return.

Ou será que não?

Hoje eu queria criar uma função que chamaria um curl pra pegar um token de autorização, e depois usaria esse token em outro curl para uma rota fornecida como argumento. Isso para que eu não tenha que sair copiando e colando tokens o dia todo e ficar passando um --header pro curl toda hora só pra trabalhar na minha API.

Então eu fiz algo mais ou menos assim:

curl-da-minha-api() {
  token=`curl ... | jq -r '.token'`
  path="$1"
  shift
  curl \
    --header "Authorization: Bearer $token" \
    --url "http://localhost:3000/$path" \
    "$@"
}
Enter fullscreen mode Exit fullscreen mode

O que eu queria era passar o caminho como primeiro argumento pra função e colocar depois de http://localhost:3000 (para que eu não tenha que escrever isso toda hora. Somos preguiçosos, certo?). Eu também queria poder passar mais opções para o segundo curl que é feito. Então eu pego o primeiro argumento com $1, faço um shift pra removê-lo da lista de argumentos, e simplesmente passo o resto pro segundo curl.

Então eu posso fazer coisas assim:

# Faz um GET para http://localhost:3000/posts
curl-da-minha-api /posts
Enter fullscreen mode Exit fullscreen mode

Mas também posso fazer coisas assim:

# Faz um POST para http://localhost:3000/posts com aquele corpo
curl-da-minha-api /posts -X POST -d '{"title": "Post Legal"}'
Enter fullscreen mode Exit fullscreen mode

Isso deixa minha função flexível, eu apenas tenho que saber que exatamente o PRIMEIRO argumento é o path. Eu poderia iterar sobre os argumentos, usar getopts ou algo assim, mas eu apenas queria fazer algo rápido e que funcionasse.

Porém!!

Quando eu fui executar essa função, eu tive um erro esquisito:

$ curl-da-minha-api /posts
curl: Command not found
Enter fullscreen mode Exit fullscreen mode

Eu pensei, o quê? Como assim? Eu tenho curl instalado. Vamos editar o arquivo onde eu defini a função:

vim ~/.zshrc
vim: Command not found
Enter fullscreen mode Exit fullscreen mode

Praticamente todo comando que eu tentei a partir daqui dava o mesmo erro, inclusive ls, cd etc.

O que está acontecendo? Por que minha função não só não funcionou, como também aparentemente estragou minha sessão no terminal?

A mensagem de erro não é muito claro. Eu pensei, vou por essa função num shell script e executá-lo com a opção -x pra conseguir debugar melhor. Mas quando eu fiz isso, a função simplesmente funcionou! Sem problemas com nada!

Depois de quebrar minha cabeça, eu tive uma ideia. Talvez a linha path=... fosse um problema. Eu renomeei a variável simplesmente para p e agora a função funcionava perfeitamente.

E ainda mais esquisito, eu tentei executar a mesma função usando nosso bom e velho amigo bash, e eu sabe o quê? Não teve nenhum problema usando a variável chamada path. Então parece que tem alguma magia misteriosa acontecendo com o zsh.

E por falar nisso, eu alterei minha função para usar local p=... quando definia a variável. Usando o local impedia a função de estragar tudo quando eu usava o nome path, assim: local path=.... Porém, a função continuava não funcionava. Interessante. E daí eu pensei, será que o zsh pode usar tanto path como PATH?

Bom, parece que sim! No zsh, se você fizer echo $path, você vai receber a mesma coisa que se fizer echo $PATH! Então aí está, como eu estava criando uma variável chamada path, ela estava sobreescrevendo a minha variável de ambiente PATH! Por isso que usar local salvou meu terminal, porque quando não usava, esse valor sobreescrito afetava tudo mesmo após a função ter terminado.

No bash, executar echo $path simplesmente não retorna nada. Eu também tentei no zsh fazer echo $home, mas isso não mostra nada também. Então estranhamente, o zsh não faz diferença entre maiúsculas e minúsculas somente para a variável do path. Mas em todas as outras variáveis que tentei, maiúsculas e minúsculas importavam, seja x, minhavariável ou shell.

Além disso, todos os meus scripts eu faço usando o shebang /sh, por isso eu não tive problemas quando copiei e colei minha função em um script. Seja lá o que estiver em /sh no meu computador, ele faz diferença entre path e PATH.

E enquanto eu escrevia isso, eu decidi googlear um pouco (mas não enquanto tentava resolver esse problema, porque a gente é preguiçoso apenas para escrever localhost, e não para depurar programas, certo?). Aparentemente, esse comportamento de usar path e PATH também está presente no csh e no tcsh. Além disso, eu ACHO que o zsh foi baseado/inspirado no tcsh e no ksh (eu acho, não me cobrem depois), então talvez venha daí essa coisa.

E também para deixar claro, não é que o zsh não diferencia maiúsuclas e minúsculas para o path. O que acontece é que ele entende ambos path e PATH, porque coisas como PaTh e PAth não funcionam). Percebi isso posteriormente com mais testes.

Pra ser sincero, eu acho isso meio esquisito e fere um pouco o "princípio da surpresa mínima" (tem esse nome em português? Estou falando disso). Mas acho que hoje aprendi
algo novo.

Aprendi que o zsh não diferencia path e PATH.
Além disso, usem local em suas funções!

Top comments (0)