Como definir e carregar sua própria função shell no zsh

55

Estou tendo dificuldade para definir e executar minhas próprias funções de shell no zsh. Segui as instruções na documentação oficial e tentei com um exemplo fácil primeiro, mas não consegui fazê-lo funcionar.

Eu tenho uma pasta:

~/.my_zsh_functions

Nesta pasta, tenho um arquivo chamado functions_1com rwxpermissões de usuário. Neste arquivo, tenho a seguinte função de shell definida:

my_function () {
echo "Hello world";
}

Eu defini FPATHpara incluir o caminho para a pasta ~/.my_zsh_functions:

export FPATH=~/.my_zsh_functions:$FPATH

Posso confirmar que a pasta .my_zsh_functionsestá no caminho das funções com echo $FPATHouecho $fpath

No entanto, se eu tentar o seguinte no shell:

> autoload my_function
> my_function

Eu recebo:

zsh: my_test_function: arquivo de definição de função não encontrado

Preciso fazer mais alguma coisa para poder ligar my_function?

Atualizar:

As respostas até agora sugerem o fornecimento do arquivo com as funções zsh. Isso faz sentido, mas estou um pouco confuso. O zsh não deveria saber onde estão esses arquivos FPATH? Qual é o propósito de autoloadentão?

Amelio Vazquez-Reina
fonte
Verifique se o $ ZDOTDIR está definido corretamente. zsh.sourceforge.net/Intro/intro_3.html
ramonovski 2/02
2
O valor de $ ZDOTDIR não está relacionado a esse problema. A variável define onde o zsh está procurando os arquivos de configuração do usuário. Se estiver desmarcado, será usado $ HOME, que é o valor certo para quase todo mundo.
24512 Frank Terbeck

Respostas:

96

No zsh, o caminho de busca da função ($ fpath) define um conjunto de diretórios, que contêm arquivos que podem ser marcados para serem carregados automaticamente quando a função que eles contêm é necessária pela primeira vez.

O Zsh possui dois modos de carregamento automático de arquivos: a maneira nativa do Zsh e outro modo que se assemelha ao carregamento automático do ksh. Este último estará ativo se a opção KSH_AUTOLOAD estiver configurada. O modo nativo do Zsh é o padrão e não discutirei o contrário aqui (consulte "man zshmisc" e "man zshoptions" para obter detalhes sobre o carregamento automático no estilo ksh).

OK. Digamos que você tenha um diretório `~ / .zfunc 'e deseje que ele faça parte do caminho de busca da função, faça o seguinte:

fpath=( ~/.zfunc "${fpath[@]}" )

Isso adiciona seu diretório privado à frente do caminho de pesquisa. Isso é importante se você deseja substituir as funções da instalação do zsh por você mesmo (como quando você deseja usar uma função de conclusão atualizada como `_git 'do repositório CVS do zsh por uma versão mais antiga do shell).

Também é importante notar que os diretórios do `$ fpath 'não são pesquisados ​​recursivamente. Se você deseja que seu diretório privado seja pesquisado recursivamente, será necessário cuidar disso, assim (o snippet a seguir requer que a opção `EXTENDED_GLOB 'seja configurada):

fpath=(
    ~/.zfuncs
    ~/.zfuncs/**/*~*/(CVS)#(/N)
    "${fpath[@]}"
)

Pode parecer enigmático para quem não é treinado, mas na verdade apenas adiciona todos os diretórios abaixo de `~ / .zfunc 'a` $ fpath', enquanto ignora os diretórios chamados "CVS" (o que é útil, se você planeja fazer o checkout completo) árvore de funções do CVS do zsh para o seu caminho de pesquisa particular).

Vamos supor que você tenha um arquivo `~ / .zfunc / hello 'que contenha a seguinte linha:

printf 'Hello world.\n'

Tudo o que você precisa fazer agora é marcar a função a ser carregada automaticamente na primeira referência:

autoload -Uz hello

"O que é o -Uz?", Você pergunta? Bem, isso é apenas um conjunto de opções que farão com que o 'carregamento automático' faça a coisa certa, independentemente de quais opções estejam sendo definidas de outra forma. O `U 'desativa a expansão do alias enquanto a função está sendo carregada e o` z' força o carregamento automático no estilo zsh, mesmo que o `KSH_AUTOLOAD 'esteja definido por qualquer motivo.

Depois disso, você poderá usar sua nova função `hello ':

zsh% olá
Olá Mundo.

Uma palavra sobre o fornecimento desses arquivos: Isso é errado . Se você tivesse originado esse arquivo `~ / .zfunc / hello ', ele imprimiria" Hello world ". uma vez. Nada mais. Nenhuma função será definida. Além disso, a idéia é carregar apenas o código da função quando necessário . Após a chamada `autoload ', a definição da função não é lida. A função está marcada apenas para ser carregada automaticamente mais tarde, conforme necessário.

E, finalmente, uma observação sobre $ FPATH e $ fpath: Zsh mantém esses parâmetros como parâmetros vinculados. O parâmetro em minúsculas é uma matriz. A versão em maiúsculas é uma cadeia de caracteres escalar, que contém as entradas da matriz vinculada unidas por dois pontos entre as entradas. Isso é feito porque o manuseio de uma lista de escalares é muito mais natural usando matrizes, além de manter a compatibilidade com versões anteriores do código que usa o parâmetro escalar. Se você optar por usar $ FPATH (o escalar), precisará ter cuidado:

FPATH=~/.zfunc:$FPATH

funcionará, enquanto o seguinte não:

FPATH="~/.zfunc:$FPATH"

O motivo é que a expansão do til não é realizada entre aspas duplas. Esta é provavelmente a fonte dos seus problemas. Se echo $FPATHimprimir um til e não um caminho expandido, não funcionará. Para estar seguro, eu usaria $ HOME em vez de um til como este:

FPATH="$HOME/.zfunc:$FPATH"

Dito isto, prefiro usar o parâmetro array como fiz no topo desta explicação.

Você também não deve exportar o parâmetro $ FPATH. É necessário apenas pelo processo atual do shell e não por nenhum de seus filhos.

Atualizar

Em relação ao conteúdo dos arquivos em `$ fpath ':

Com o carregamento automático no estilo zsh, o conteúdo de um arquivo é o corpo da função que ele define. Assim, um arquivo chamado "hello" contendo uma linha echo "Hello world."define completamente uma função chamada "hello". Você é livre para colocar hello () { ... }o código em prática , mas isso seria supérfluo.

A alegação de que um arquivo pode conter apenas uma função não está totalmente correta.

Especialmente se você observar algumas funções do sistema de conclusão baseado em funções (compsys), rapidamente perceberá que isso é um equívoco. Você é livre para definir funções adicionais em um arquivo de função. Você também é livre para fazer qualquer tipo de inicialização, o que pode ser necessário na primeira vez que a função é chamada. No entanto, quando você definir, sempre definirá uma função chamada como o arquivo no arquivo e chamará essa função no final do arquivo, para que seja executada na primeira vez em que a função for referenciada.

Se - com subfunções - você não definiu uma função chamada como o arquivo no arquivo, você acabaria com essa função com definições de funções (a saber, as subfunções no arquivo). Você definiria efetivamente todas as suas subfunções toda vez que chamar a função denominada como o arquivo. Normalmente, não é isso que você deseja, então você redefiniria uma função, denominada como o arquivo dentro do arquivo.

Vou incluir um esqueleto curto, que lhe dará uma idéia de como isso funciona:

# Let's again assume that these are the contents of a file called "hello".

# You may run arbitrary code in here, that will run the first time the
# function is referenced. Commonly, that is initialisation code. For example
# the `_tmux' completion function does exactly that.
echo initialising...

# You may also define additional functions in here. Note, that these
# functions are visible in global scope, so it is paramount to take
# care when you're naming these so you do not shadow existing commands or
# redefine existing functions.
hello_helper_one () {
    printf 'Hello'
}

hello_helper_two () {
    printf 'world.'
}

# Now you should redefine the "hello" function (which currently contains
# all the code from the file) to something that covers its actual
# functionality. After that, the two helper functions along with the core
# function will be defined and visible in global scope.
hello () {
    printf '%s %s\n' "$(hello_helper_one)" "$(hello_helper_two)"
}

# Finally run the redefined function with the same arguments as the current
# run. If this is left out, the functionality implemented by the newly
# defined "hello" function is not executed upon its first call. So:
hello "$@"

Se você executasse este exemplo bobo, a primeira execução seria assim:

zsh% olá
inicializando ...
Olá Mundo.

E as chamadas consecutivas ficarão assim:

zsh% olá
Olá Mundo.

Eu espero que isso esclareça as coisas.

(Um dos exemplos mais complexos do mundo real que usa todos esses truques é a função ` _tmux ' já mencionada no sistema de conclusão baseado em funções do zsh.)

Frank Terbeck
fonte
Obrigado Frank! Li nas outras respostas que só consigo definir uma função por arquivo, certo? Notei que você não usou a sintaxe my_function () { }no seu Hello worldexemplo. Se a sintaxe não for necessária, quando seria útil usá-la?
Amelio Vazquez-Reina
11
Estendi a resposta original para responder a essas perguntas também.
31812 Frank Terbeck
“Você sempre definirá a função denominada como o arquivo no arquivo e chamará essa função no final do arquivo”: por que?
precisa saber é o seguinte
Hibou57: (Além disso: esse é um erro de digitação, deve-se "definir uma função", que foi corrigida agora.) Pensei que estava claro quando você leva em consideração o trecho de código a seguir. Enfim, adicionei um parágrafo que explica a razão um pouco mais literalmente.
precisa saber é o seguinte
Aqui está mais informações do seu site, obrigado.
Timo
5

O nome do arquivo em um diretório nomeado por um fpathelemento deve corresponder ao nome da função de carregamento automático que ele define.

Sua função é nomeada my_functione ~/.my_zsh_functionsé o diretório pretendido no seu fpath, portanto a definição de my_functiondeve estar no arquivo ~/.my_zsh_functions/my_function.

O plural no nome do arquivo proposto ( functions_1) indica que você estava planejando colocar várias funções no arquivo. Não é assim que o fpathcarregamento automático funciona. Você deve ter uma definição de função por arquivo.

Chris Johnsen
fonte
2

ceder source ~/.my_zsh_functions/functions1no terminal e avaliar my_function, agora você poderá chamar a função

harish.venkat
fonte
2
Obrigado, mas qual é o papel FPATHe autoloadentão? Por que eu também preciso originar o arquivo? Veja minha pergunta atualizada.
Amelio Vazquez-Reina
1

Você pode "carregar" um arquivo com todas as suas funções no seu $ ZDOTDIR / .zshrc assim:

source $ZDOTDIR/functions_file

Ou pode usar um ponto "." em vez de "fonte".

ramonovski
fonte
11
Obrigado, mas qual é o papel FPATHe autoloadentão? Por que eu também preciso originar o arquivo? Veja minha pergunta atualizada.
Amelio Vazquez-Reina
0

Definitivamente, a terceirização não é a abordagem correta, pois o que você deseja é ter funções inicializadas preguiçosamente. É para isso que autoloadserve. Veja como você realiza o que procura.

No seu ~/.my_zsh_functions, você diz que deseja colocar uma função chamada my_functionechos "olá mundo". Mas você o envolve em uma chamada de função, que não é como isso funciona. Em vez disso, você precisa criar um arquivo chamado ~/.my_zsh_functions/my_function. Nele, basta colocar echo "Hello world", não em um wrapper de função. Você também pode fazer algo assim se realmente preferir ter o invólucro.

# ~/.my_zsh_functions/my_function
__my_function () {
    echo "Hello world";
}
# you have to call __my_function
# if this is how you choose to do it
__my_function

Em seguida, no seu .zshrcarquivo, adicione o seguinte:

fpath=(~/.my_zsh_functions $fpath);
autoload -U ~/.my_zsh_functions/my_function

Ao carregar um novo shell ZSH, digite which my_function. Você deve ver isso:

my_function () {
    # undefined
    builtin autoload -XU
}

ZSH acabou de apagar minha_função para você autoload -X. Agora, corra, my_functionmas apenas digite my_function. Você deverá ver a Hello worldimpressão e agora, quando executar, which my_functiondeverá ver a função preenchida assim:

my_function () {
    echo "Hello world"
}

Agora, a verdadeira mágica vem quando você configura toda a ~/.my_zsh_functionspasta para trabalhar autoload. Se você deseja que cada arquivo que você solte nesta pasta funcione dessa maneira, altere o que você coloca no seu .zshrcpara algo como isto:

# add ~/.my_zsh_functions to fpath, and then lazy autoload
# every file in there as a function
fpath=(~/.my_zsh_functions $fpath);
autoload -U fpath[1]/*(.:t)
mattmc3
fonte