AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / unix / Perguntas / 470440
Accepted
user149572
user149572
Asked: 2018-09-21 20:22:30 +0800 CST2018-09-21 20:22:30 +0800 CST 2018-09-21 20:22:30 +0800 CST

O conceito de "retorno de chamada" de programação existe no Bash?

  • 772

Algumas vezes, quando li sobre programação, me deparei com o conceito de "retorno de chamada".

Curiosamente, nunca encontrei uma explicação que eu possa chamar de "didática" ou "clara" para esse termo "função de retorno de chamada" (quase qualquer explicação que li me pareceu bastante diferente de outra e me senti confuso).

O conceito de "retorno de chamada" de programação existe no Bash? Em caso afirmativo, responda com um pequeno e simples exemplo Bash.

bash function
  • 7 7 respostas
  • 9819 Views

7 respostas

  • Voted
  1. Best Answer
    Stephen Kitt
    2018-09-21T23:10:39+08:002018-09-21T23:10:39+08:00

    Na programação imperativa típica , você escreve sequências de instruções e elas são executadas uma após a outra, com fluxo de controle explícito. Por exemplo:

    if [ -f file1 ]; then   # If file1 exists ...
        cp file1 file2      # ... create file2 as a copy of a file1
    fi
    

    etc.

    Como pode ser visto no exemplo, na programação imperativa você segue o fluxo de execução com bastante facilidade, sempre trabalhando a partir de qualquer linha de código para determinar seu contexto de execução, sabendo que quaisquer instruções que você der serão executadas como resultado de sua localização no fluxo (ou locais de seus sites de chamada, se você estiver escrevendo funções).

    Como os retornos de chamada alteram o fluxo

    Quando você usa callbacks, ao invés de colocar o uso de um conjunto de instruções “geograficamente”, você descreve quando ele deve ser chamado. Exemplos típicos em outros ambientes de programação são casos como “baixar este recurso e, quando o download estiver concluído, chamar este retorno de chamada”. O Bash não possui uma construção genérica de retorno de chamada desse tipo, mas possui retornos de chamada, para tratamento de erros e algumas outras situações; por exemplo (é preciso primeiro entender a substituição de comandos e os modos de saída do Bash para entender esse exemplo):

    #!/bin/bash
    
    scripttmp=$(mktemp -d)           # Create a temporary directory (these will usually be created under /tmp or /var/tmp/)
    
    cleanup() {                      # Declare a cleanup function
        rm -rf "${scripttmp}"        # ... which deletes the temporary directory we just created
    }
    
    trap cleanup EXIT                # Ask Bash to call cleanup on exit
    

    Se você quiser experimentar isso sozinho, salve o acima em um arquivo, digamos cleanUpOnExit.sh, torne-o executável e execute-o:

    chmod 755 cleanUpOnExit.sh
    ./cleanUpOnExit.sh
    

    Meu código aqui nunca chama explicitamente a cleanupfunção; ele diz ao Bash quando chamá-lo, usando trap cleanup EXIT, ou seja , “querido Bash, por favor, execute o cleanupcomando quando você sair” (e cleanupacontece de ser uma função que eu defini anteriormente, mas pode ser qualquer coisa que o Bash entenda). O Bash suporta isso para todos os sinais não fatais, saídas, falhas de comando e depuração geral (você pode especificar um retorno de chamada que é executado antes de cada comando). O retorno de chamada aqui é a cleanupfunção, que é “chamada de volta” pelo Bash logo antes do shell sair.

    Você pode usar a habilidade do Bash para avaliar parâmetros de shell como comandos, para construir uma estrutura orientada a retorno de chamada; isso está um pouco além do escopo desta resposta e talvez cause mais confusão ao sugerir que passar funções sempre envolve retornos de chamada. Consulte Bash: passe uma função como parâmetro para alguns exemplos da funcionalidade subjacente. A ideia aqui, como nos callbacks de manipulação de eventos, é que as funções podem receber dados como parâmetros, mas também outras funções — isso permite que os chamadores forneçam comportamento e dados. Um exemplo simples dessa abordagem pode ser

    #!/bin/bash
    
    doonall() {
        command="$1"
        shift
        for arg; do
            "${command}" "${arg}"
        done
    }
    
    backup() {
        mkdir -p ~/backup
        cp "$1" ~/backup
    }
    
    doonall backup "$@"
    

    (Eu sei que isso é um pouco inútil, pois cppode lidar com vários arquivos, é apenas para ilustração.)

    Aqui criamos uma função, doonall, que recebe outro comando, dado como parâmetro, e o aplica aos demais parâmetros; então usamos isso para chamar a backupfunção em todos os parâmetros fornecidos ao script. O resultado é um script que copia todos os seus argumentos, um por um, para um diretório de backup.

    Esse tipo de abordagem permite que funções sejam escritas com responsabilidades únicas: a responsabilidade doonallde ' é executar algo em todos os seus argumentos, um de cada vez; backupA responsabilidade de é fazer uma cópia de seu (único) argumento em um diretório de backup. Ambos doonalle backuppodem ser usados ​​em outros contextos, o que permite mais reutilização de código, melhores testes etc.

    Nesse caso, o retorno de chamada é a backupfunção, que dizemos doonallpara “chamar de volta” em cada um de seus outros argumentos — fornecemos doonallcomportamento (seu primeiro argumento) e dados (os argumentos restantes).

    (Observe que no tipo de caso de uso demonstrado no segundo exemplo, eu não usaria o termo "retorno de chamada", mas talvez seja um hábito resultante das linguagens que uso. Penso nisso como passar funções ou lambdas ao redor , em vez de registrar retornos de chamada em um sistema orientado a eventos.)

    • 52
  2. Gilles 'SO- stop being evil'
    2018-09-21T23:10:47+08:002018-09-21T23:10:47+08:00

    Primeiro é importante notar que o que torna uma função uma função de retorno de chamada é como ela é usada, não o que ela faz. Um retorno de chamada é quando o código que você escreve é ​​chamado de um código que você não escreveu. Você está pedindo ao sistema para ligar de volta quando algum evento específico acontecer.

    Um exemplo de retorno de chamada na programação shell são as armadilhas. Uma armadilha é um retorno de chamada que não é expresso como uma função, mas como um pedaço de código a ser avaliado. Você está pedindo ao shell para chamar seu código quando o shell recebe um sinal específico.

    Outro exemplo de retorno de chamada é a -execação do findcomando. O trabalho do findcomando é percorrer os diretórios recursivamente e processar cada arquivo por vez. Por padrão, o processamento é imprimir o nome do arquivo (implícito -print), mas com -execo processamento é executar um comando que você especificar. Isso se encaixa na definição de um retorno de chamada, embora não seja muito flexível, pois o retorno de chamada é executado em um processo separado.

    Se você implementou uma função do tipo find, você pode fazer com que ela use uma função de retorno de chamada para chamar cada arquivo. Aqui está uma função tipo find ultra-simplificada que recebe um nome de função (ou nome de comando externo) como argumento e o chama em todos os arquivos regulares no diretório atual e seus subdiretórios. A função é usada como um retorno de chamada que é chamado toda vez call_on_regular_filesque encontra um arquivo regular.

    shopt -s globstar
    call_on_regular_files () {
      declare callback="$1"
      declare file
      for file in **/*; do
        if [[ -f $file ]]; then
          "$callback" "$file"
        fi
      done
    }
    

    Os retornos de chamada não são tão comuns na programação de shell como em alguns outros ambientes porque os shells são projetados principalmente para programas simples. Os retornos de chamada são mais comuns em ambientes em que o fluxo de dados e controle é mais propenso a alternar entre partes do código que são escritas e distribuídas independentemente: o sistema básico, várias bibliotecas, o código do aplicativo.

    • 27
  3. mosvy
    2018-09-22T00:52:46+08:002018-09-22T00:52:46+08:00

    "callbacks" são apenas funções passadas como argumentos para outras funções.

    No nível do shell, isso significa simplesmente scripts/funções/comandos passados ​​como argumentos para outros scripts/funções/comandos.

    Agora, para um exemplo simples, considere o seguinte script:

    $ cat ~/w/bin/x
    #! /bin/bash
    cmd=$1; shift
    case $1 in *%*) flt=${1//\%/\'%s\'};; *) flt="$1 '%s'";; esac; shift
    q="'\\''"; f=${flt//\\/'\\'}; p=`printf "<($f) " "${@//\'/$q}"`
    eval "$cmd" "$p"
    

    tendo a sinopse

    x command filter [file ...]
    

    será aplicado filtera cada fileargumento e, em seguida, chamará commandcom as saídas dos filtros como argumentos.

    Por exemplo:

    x diff zcat a.gz b.bz   # diff gzipped files
    x diff3 zcat a.gz b.gz c.gz   # same with three-way diff
    x diff hd a b  # hex diff of binary files
    x diff 'zcat % | sort -u' a.gz b.gz  # first uncompress the files, then sort+uniq them, then compare them
    x 'comm -12' sort a b  # find common lines in unsorted files
    

    Isso é muito próximo do que você pode fazer em lisp (brincadeira ;-))

    Algumas pessoas insistem em limitar o termo "callback" para "manipulador de eventos" e/ou "fechamento" (função + tupla de dados/ambiente); este não é de forma alguma o significado geralmente aceito . E uma razão pela qual "callbacks" nesses sentidos estreitos não são muito úteis no shell é porque pipes + paralelismo + recursos de programação dinâmica são muito mais poderosos, e você já está pagando por eles em termos de desempenho, mesmo se você tente usar o shell como uma versão desajeitada de perlou python.

    • 7
  4. user1934428
    2018-09-22T00:27:15+08:002018-09-22T00:27:15+08:00

    Tipo de.

    Uma maneira simples de implementar um retorno de chamada no bash é aceitar o nome de um programa como parâmetro, que atua como "função de retorno de chamada".

    # This is script worker.sh accepts a callback in $1
    cb="$1"
    ....
    # Execute the call back, passing 3 parameters
    $cb foo bar baz
    

    Isso seria usado assim:

    # Invokes mycb.sh as a callback
    worker.sh mycb.sh
    

    Claro que você não tem encerramentos no bash. Portanto, a função de retorno de chamada não tem acesso às variáveis ​​do lado do chamador. Você pode, no entanto, armazenar dados que o retorno de chamada precisa em variáveis ​​de ambiente. Passar informações do retorno de chamada para o script do invocador é mais complicado. Os dados podem ser colocados em um arquivo.

    Se o seu design permite que tudo seja tratado em um único processo, você pode usar uma função shell para o retorno de chamada e, nesse caso, a função de retorno de chamada tem, é claro, acesso às variáveis ​​no lado do invocador.

    • 4
  5. user147505
    2018-09-22T02:19:31+08:002018-09-22T02:19:31+08:00

    Apenas para adicionar algumas palavras às outras respostas. O retorno de chamada da função opera em funções externas à função que chama de volta. Para que isso seja possível, uma definição completa da função a ser chamada de volta precisa ser passada para a função que está chamando de volta, ou seu código deve estar disponível para a função que está chamando de volta.

    O primeiro (passar código para outra função) é possível, embora eu pule um exemplo, pois isso envolveria complexidade. O último (passar a função pelo nome) é uma prática comum, pois as variáveis ​​e funções declaradas fora do escopo de uma função estão disponíveis nessa função desde que sua definição anteceda a chamada para a função que opera nelas (que, por sua vez, , para ser declarado antes de ser chamado).

    Observe também que algo semelhante acontece quando as funções são exportadas. Um shell que importa uma função pode ter uma estrutura pronta e estar apenas esperando as definições de função para colocá-las em ação. A exportação da função está presente no Bash e causou problemas anteriormente sérios, btw (que se chamava Shellshock):

    • O que significa env x='() { :;}; command' bash do e por que é inseguro?
    • Quando o bug shellshock (CVE-2014-6271/7169) foi introduzido e qual é o patch que o corrige totalmente?

    Vou completar esta resposta com mais um método de passar uma função para outra função, que não está explicitamente presente no Bash. Este está passando por endereço, não por nome. Isso pode ser encontrado em Perl, por exemplo. O Bash não oferece esse caminho nem para funções nem para variáveis. Mas se, como você afirma, você deseja ter uma imagem mais ampla com Bash como apenas um exemplo, então você deve saber que o código da função pode residir em algum lugar da memória e esse código pode ser acessado por esse local de memória, que é chamou seu endereço.

    • 3
  6. slebetman
    2018-09-24T00:13:39+08:002018-09-24T00:13:39+08:00

    Um dos exemplos mais simples de retorno de chamada no bash é aquele com o qual muitas pessoas estão familiarizadas, mas não percebem qual padrão de design elas estão realmente usando:

    cron

    Cron permite que você especifique um executável (um binário ou script) que o programa cron chamará de volta quando algumas condições forem atendidas (a especificação de tempo)

    Digamos que você tenha um script chamado doEveryDay.sh. A maneira sem retorno de chamada de escrever o script é:

    #! /bin/bash
    while true; do
        doSomething
        sleep $TWENTY_FOUR_HOURS
    done
    

    A maneira de retorno de chamada para escrevê-lo é simplesmente:

    #! /bin/bash
    doSomething
    

    Então no crontab você definiria algo como

    0 0 * * *     doEveryDay.sh
    

    Você não precisaria escrever o código para esperar que o evento fosse acionado, mas, em vez disso, confiaria em cronchamar seu código de volta.


    Agora, considere COMO você escreveria esse código no bash.

    Como você executaria outro script/função no bash?

    Vamos escrever uma função:

    function every24hours () {
        CALLBACK=$1 ;# assume the only argument passed is
                     # something we can "call"/execute
        while true; do
            $CALLBACK ;# simply call the callback
            sleep $TWENTY_FOUR_HOURS
        done
    }
    

    Agora você criou uma função que aceita um retorno de chamada. Você pode simplesmente chamar assim:

    # "ping" google website every day
    every24hours 'curl google.com'
    

    Claro, a função every24hours nunca retorna. Bash é um pouco único, pois podemos facilmente torná-lo assíncrono e gerar um processo anexando &:

    every24hours 'curl google.com' &
    

    Se você não quiser isso como uma função, você pode fazer isso como um script:

    #every24hours.sh
    CALLBACK=$1 ;# assume the only argument passed is
                   # something we can "call"/execute
    while true; do
        $CALLBACK ;# simply call the callback
        sleep $TWENTY_FOUR_HOURS
    done
    

    Como você pode ver, os retornos de chamada no bash são triviais. É simplesmente:

    CALLBACK_SCRIPT=$3 ;# or some other 
                        # argument to 
                        # function/script
    

    E chamar o retorno de chamada é simplesmente:

    $SOME_CALLBACK_FUNCTION_OR_SCRIPT
    

    Como você pode ver no formulário acima, os retornos de chamada raramente são recursos diretos dos idiomas. Eles geralmente estão programando de maneira criativa usando recursos de linguagem existentes. Qualquer linguagem que possa armazenar um ponteiro/referência/cópia de algum bloco de código/função/script pode implementar retornos de chamada.

    • 2
  7. jlliagre
    2018-09-25T14:58:12+08:002018-09-25T14:58:12+08:00

    Um retorno de chamada é uma função chamada quando ocorre algum evento. Combash o , o único mecanismo de manipulação de eventos em vigor está relacionado a sinais, a saída do shell e estendido para eventos de erros do shell, eventos de depuração e eventos de retorno de scripts de função/origem.

    Aqui está um exemplo de um retorno de chamada inútil, mas simples, aproveitando as armadilhas de sinal.

    Primeiro crie o script implementando o retorno de chamada:

    #!/bin/bash
    
    myCallback() {
        echo "I've been called at $(date +%Y%m%dT%H%M%S)"
    }
    
    # Set the handler
    trap myCallback SIGUSR1
    
    # Main loop. Does nothing useful, essentially waits
    while true; do
        read foo
    done
    

    Em seguida, execute o script em um terminal:

    $ ./callback-example

    e em outro, enviar o USR1sinal para o processo shell.

    $ pkill -USR1 callback-example
    

    Cada sinal enviado deve acionar a exibição de linhas como estas no primeiro terminal:

    I've been called at 20180925T003515
    I've been called at 20180925T003517
    

    ksh93, como shell implementando muitos recursos que bashforam adotados posteriormente, fornece o que chama de "funções de disciplina". Essas funções, não disponíveis com bash, são chamadas quando uma variável shell é modificada ou referenciada (ou seja, lida). Isso abre o caminho para aplicativos orientados a eventos mais interessantes.

    Por exemplo, esse recurso permitia que retornos de chamada no estilo X11/Xt/Motif em widgets gráficos fossem implementados em uma versão antiga kshque incluía extensões gráficas chamadas dtksh. Consulte o manual do dksh .

    • 1

relate perguntas

  • exportar variáveis ​​​​env programaticamente, via stdout do comando [duplicado]

  • Problema estranho ao passar variáveis ​​do arquivo de texto

  • Enquanto a linha lê mantendo os espaços de escape?

  • ordem de substituição de processos `te` e `bash`

  • Execute um script muito lento até que seja bem-sucedido

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Como exportar uma chave privada GPG e uma chave pública para um arquivo

    • 4 respostas
  • Marko Smith

    ssh Não é possível negociar: "nenhuma cifra correspondente encontrada", está rejeitando o cbc

    • 4 respostas
  • Marko Smith

    Como podemos executar um comando armazenado em uma variável?

    • 5 respostas
  • Marko Smith

    Como configurar o systemd-resolved e o systemd-networkd para usar o servidor DNS local para resolver domínios locais e o servidor DNS remoto para domínios remotos?

    • 3 respostas
  • Marko Smith

    Como descarregar o módulo do kernel 'nvidia-drm'?

    • 13 respostas
  • Marko Smith

    apt-get update error no Kali Linux após a atualização do dist [duplicado]

    • 2 respostas
  • Marko Smith

    Como ver as últimas linhas x do log de serviço systemctl

    • 5 respostas
  • Marko Smith

    Nano - pule para o final do arquivo

    • 8 respostas
  • Marko Smith

    erro grub: você precisa carregar o kernel primeiro

    • 4 respostas
  • Marko Smith

    Como baixar o pacote não instalá-lo com o comando apt-get?

    • 7 respostas
  • Martin Hope
    rocky Como exportar uma chave privada GPG e uma chave pública para um arquivo 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Wong Jia Hau ssh-add retorna com: "Erro ao conectar ao agente: nenhum arquivo ou diretório" 2018-08-24 23:28:13 +0800 CST
  • Martin Hope
    Evan Carroll status systemctl mostra: "Estado: degradado" 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim Como podemos executar um comando armazenado em uma variável? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S Por que /dev/null é um arquivo? Por que sua função não é implementada como um programa simples? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 Como ver as últimas linhas x do log de serviço systemctl 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - pule para o final do arquivo 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla Por que verdadeiro e falso são tão grandes? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis Substitua a string em um arquivo de texto enorme (70 GB), uma linha 2017-12-30 06:58:33 +0800 CST
  • Martin Hope
    Bagas Sanjaya Por que o Linux usa LF como caractere de nova linha? 2017-12-20 05:48:21 +0800 CST

Hot tag

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve