Processar a função 'zsh' com o utilitário que reconhece apenas comandos

6

(Esta questão usa a ferramenta Mac caffeinate como exemplo, mas o conceito se aplica a todas as ferramentas (por exemplo, xargs ) que aceitam um utilitário como argumento.)

A ferramenta caffeinate do Mac aceita o nome de um utilitário: caffeinate sleep 1 , por exemplo, (em que sleep é o utilitário). Existe alguma maneira de aceitar uma função zsh , sem modificar a própria ferramenta? Por exemplo:

function mysleep {
  sleep 2
}

caffeinate mysleep    # mysleep: No such file or directory

Edit: Esta questão é de fato uma duplicata para o Bash - obrigado por me apontar a resposta. Para zsh, no entanto, export -f não funciona. Existe uma maneira de fazer isso em zsh? (Estou removendo a tag bash para reduzir a confusão).

    
por Daniel Li 02.06.2017 / 01:54

2 respostas

2

caffeinate espera executar uma comando em um novo processo.

Para interpretar uma função zsh , você precisa de um comando zsh .

E você precisaria passar a definição dessa função (assim como outras funções que ela pode precisar) para ela, por exemplo com:

mysleep() {
  sleep 2
}
caffeinate zsh -c "$(functions mysleep);mysleep"

functions mysleep despeja a definição da função mysleep que passamos para o novo zsh para interpretação antes de chamar a função, para que zsh invocado por caffeinate acabe interpretando:

mysleep() {
  sleep 2
};mysleep

Se compararmos a bash :

mysleep() {
  sleep 2
}
export -f mysleep
caffeinate bash -c "mysleep"

(que é 2 caracteres mais curto para digitar), bash fará:

execve("/path/to/caffeinate",
  ["caffeinate", "bash", "-c", "mysleep"],
  ["BASH_FUNC_mysleep%%=() {  sleep 2\n}", rest-of-environment])

Enquanto com zsh , obtemos:

execve("/path/to/caffeinate",
  ["caffeinate", "zsh", "-c", "mysleep () {\n\tsleep 2\n};mysleep"],
  [rest-of-environment])

Eu vejo várias vantagens dessa última abordagem:

  • temos controle total: sabemos como passamos a definição da função, como ela está sendo usada. Há menos espaço para coisas desagradáveis como o tipo de shellshock aqui.
  • como o nome da variável de ambiente bash que transporta a definição de função contém % caracteres (e mesmo que não tenha, pense em sudo por exemplo), não temos garantia de que caffeinate será propagá-lo para o comando bash que ele executa.
  • se for propagado, porque a definição da função é armazenada em envp [] em vez de argv [], isso significa que polui o ambiente de todos os outros comandos executados nesse ambiente (incluindo sleep , por exemplo, neste exemplo).
  • (menor), embora o código shell bash seja menor, mais dados são transmitidos para execve() , de modo que contribui mais para o limite E2BIG dessa chamada de sistema.

Se você quiser usar o ambiente, ainda é possível fazer isso:

FUNCS=$(functions mysleep) caffeinate zsh -c '
  eval "$FUNCS";mysleep'

No caso de caffeinate aqui, é onde precisamos apenas que caffeinate seja executado enquanto a função está em execução, não necessariamente para executar a função, podemos usar outras abordagens como:

mysleep | caffeinate cat

cat será executado enquanto mysleep for executado. mysleep ainda seria executado em um processo separado e isso afeta o stdout de mysleep .

mysleep 3> {fd}>(caffeinate cat)

resolveria os dois problemas.

Como acima, isso cria um canal entre mysleep e cat . Mas a extremidade de gravação do pipe agora está em um descritor de arquivo alocado recentemente acima de 10 (armazenado em $fd ) para o qual mysleep normalmente não grava. Portanto, cat não lerá nada, mas esperará até o final do arquivo no canal, o que só acontecerá quando mysleep (e todos os processos filhos que herdam esse fd) terminar.

    
por 02.06.2017 / 08:12
0

A melhor maneira de fazer isso é simplesmente colocar sua função em um arquivo de script e chamar o script como tal:

caffeinate myfunction.sh

Conteúdo do myfunction.sh:

#!/bin/bash
sleep 2

verifique se o arquivo de script é executável ou se você tem um erro de permissão:

chmod +x myfunction.sh
    
por 02.06.2017 / 06:29

Tags