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 / computer / Perguntas / 1603249
Accepted
MichaIng
MichaIng
Asked: 2020-11-19 13:36:05 +0800 CST2020-11-19 13:36:05 +0800 CST 2020-11-19 13:36:05 +0800 CST

Leia a saída do console sem redirecionamento ou pipe

  • 772

Existe uma maneira de ler a saída do console de um comando sem redirecionar ou canalizar seu STDOUT/STDERR?

O problema com redirecionamentos ou pipes é que alguns comandos se comportam de forma diferente quando seus STDOUT e/ou STDERR são redirecionados, por exemplo, cor ou algum formato é removido, ou diferenças mais relevantes. Versões mais tputantigas requerem STDOUT ou STDERR em um console regular para ler suas dimensões. No caso de pipes, adicionalmente, o comando originating perde a capacidade de controlar o shell de origem, por exemplo, exitum script de dentro de uma função, que tem sua saída canalizada, não é possível.

O que eu quero conseguir é executar um comando para que ele imprima sua saída no console diretamente, seja capaz de matar/sair do shell, mas analisar/manipular e, no caso, registrar sua saída. teeseria uma solução óbvia, mas sofre dos problemas mencionados.

Fiz algumas tentativas com read, executando um loop que tenta ler os descritores de arquivo do comando ou /dev/stdoutou /dev/tty, mas em todos os casos, nem uma única linha da saída do comando mostrado é realmente read.

Por exemplo

#!/bin/bash
apt update 2>&1 & pid=$!
while [[ -f /proc/$pid/fd/1 ]] && read -r line
do
        echo "$line" >> ./testfile
done < /proc/$pid/fd/1

mas também executando o loop while em segundo plano e o comando em primeiro plano (IMO preferível), como:

#!/bin/bash
while read -r line
do
        echo "$line" >> ./testfile
done < /dev/tty & pid=$!
apt update
kill $pid

mas em ambos os casos ./testfilepermanece vazio.

/dev/stdouté o próprio descritor de arquivo STDOUT de cada processo, que não pode funcionar, é claro.

Provavelmente alguém tem uma idéia de como conseguir isso ou uma alternativa semelhante?

linux bash
  • 1 1 respostas
  • 1557 Views

1 respostas

  • Voted
  1. Best Answer
    Kamil Maciorowski
    2020-11-21T04:59:58+08:002020-11-21T04:59:58+08:00

    Nota preliminar

    Nesta resposta, uso o termo "tty" para qualquer dispositivo terminal, geralmente para pts .


    Abordagem simples

    Existem ferramentas que você pode usar para capturar a saída de um comando enquanto ainda fornece um tty para ele: unbuffer, script, expect, screen, tmux, ssh, possivelmente outros.

    Para um único comando externo, isso é relativamente fácil. Pegue ls --color=auto. Esta tentativa:

    ls --color=auto | tee ./log
    

    "sofre dos problemas mencionados", como você notou. Mas isso:

    unbuffer ls --color=auto | tee ./log         # or tee -a
    

    imprime muito bem saídas coloridas e em colunas e armazena uma cópia completa no formato ./log. Assim faz isso:

    script -qc 'ls --color=auto' ./log           # or script -a
    

    embora neste caso haja um cabeçalho e um rodapé no arquivo, você pode gostar ou não.

    Eu não vou detalhar expect, screenou tmux. Como último recurso (quando nenhuma outra ferramenta estiver disponível), pode-se usar sshdepois de configurar o acesso SSH sem senha do localhost para si mesmo. Algo assim:

    ssh -tt localhost "cd ${PWD@Q} && ls --color=auto" | tee ./log
    

    ( ${var@Q}expande para o valor de varquote em um formato que pode ser reutilizado como entrada; perfeito aqui. O shell que é executado sshdeve ser Bash, a sintaxe não é portátil.)

    unbufferparece a solução mais simples. O nome sugere que seu objetivo principal é desabilitar o buffer, mas cria um pseudo-terminal .


    Complicações

    Você deseja capturar a saída também de uma função de shell, sem perder sua conexão com o shell principal que interpreta o script. Para isso a função deve ser executada no shell principal, a abordagem simples acima com uma ferramenta que executa algum comando externo não pode ser usada, a menos que o comando externo seja o script inteiro:

    unbuffer ./the-script | tee ./log
    

    Obviamente, essa solução não é intrínseca ao script. Acho que você quer simplesmente executar ./the-scripte capturar a saída à medida que ela vai para o terminal. Portanto, o script precisa criar um tty "capturável" para si mesmo de alguma forma. Esta é a parte complicada.


    Solução possível

    Uma possível solução é executar

    unbuffer something | tee ./log &         # or tee -a
    

    e redirecionar descritores de arquivo 1e (opcionalmente) 2do shell principal para o tty criado para something. somethingdeve sentar-se silenciosamente e não fazer (quase) nada.

    Vantagens:

    • Você pode salvar os descritores de arquivo originais como números diferentes, então você pode parar de registrar a qualquer momento redirecionando stdin e stdout de volta para o que eles eram.
    • Você pode executar vários unbuffer … | tee … &descritores de arquivo e fazer malabarismos para registrar a saída de diferentes partes do script em arquivos diferentes.
    • Você pode redirecionar seletivamente stdout e/ou stderr de qualquer comando único.

    Desvantagens:

    • O script deve kill unbufferou somethingquando o registro não for mais necessário. Deve fazer isso quando sair normalmente ou por causa de um sinal. Se for morto à força, não será capaz de fazer isso. Talvez somethingdevesse verificar periodicamente se o processo principal ainda está lá e sair eventualmente. Há uma solução bacana com flock(veja abaixo).

    • somethingprecisa relatar seu tty para o shell principal de alguma forma. Apenas imprimir a saída de ttyé uma possibilidade, o shell principal seria aberto ./logindependentemente e recuperaria as informações. Depois disso, é apenas lixo no arquivo (e na tela do terminal original). O script pode truncar o arquivo, isso só funcionará com tee -a(porque tee -avs teeé como >>vs >nesta minha resposta ). É melhor somethingpassar a informação por um canal separado: um arquivo temporário ou um fifo nomeado criado apenas para isso.


    Prova de conceito

    O código a seguir precisa estar unbufferassociado a expect(no Debian: expectpacote) e flock(no Debian: util-linuxpacote).

    #!/bin/bash
    
    save-stdout-stderr() {
       exec 7>&1 8>&2
    }
    
    restore-stdout-stderr() {
       exec 1>&7 2>&8 7>&- 8>&-
    }
    
    create-logging-tty() {
     # usage: create-logging-tty descriptor log
       local tmpdir tmpfifo tmpdesc tty descriptor log
       descriptor="$1"
       log="$2"
       tmpdir="$(mktemp -d)"
       tmpfifo="$tmpdir/fifo"
       mkfifo "$tmpfifo"
       eval 'exec '"$descriptor"'>/dev/null'
       exec {tmpdesc}<>"$tmpfifo"
       flock "$tmpdesc"
       unbuffer sh -c '
          exec 3<>"$1"
          tty >&3
          flock 3
          flock 2
       ' sh "$tmpfifo" | tee "$log" &
       if ! IFS= read -ru "$tmpdesc" -t 5 tty; then
          rm -r "$tmpdir"
          exec {descriptor}>&-
          flock -u "$tmpdesc"
          return 1
       fi
       rm -r "$tmpdir"
       eval 'exec '"$descriptor"'> "$tty"'
       flock "$descriptor"
       flock -u "$tmpdesc"
    }
    
    destroy-logging-tty() {
     # usage: destroy-logging-tty descriptor
       local descriptor
       descriptor="$1"
       flock -u "$descriptor"
       exec {descriptor}>&-
    }
    
    # here the actual script begins
    
    save-stdout-stderr
    
    echo "This won't be logged."
    
    create-logging-tty 21 ./log
    exec 1>&21 2>&21
    
    echo "This will be logged."
    
    # proof of concept
    ls --color=auto /dev
    
    restore-stdout-stderr
    destroy-logging-tty 21
    
    echo "This won't be logged."
    

    Notas:

    • save-stdout-stderre restore-stdout-stderruse valores codificados 7e 8. Você não deve usar esses descritores para mais nada. Reconstrua essa funcionalidade, se necessário.

    • create-logging-tty 21 ./logé uma solicitação para criar um descritor de arquivo 21(número arbitrário) que seria um tty registrado em ./log(nome de caminho arbitrário). A função deve ser chamada do shell principal (não de um subshell) porque deve criar um descritor de arquivo para o shell principal.

    • create-logging-ttyusa evalpara criar um descritor de arquivo com o número solicitado. evalpode ser mau , mas aqui é seguro , a menos que você passe algum código de shell infeliz (ou desonesto) em vez de um número. A função não verifica se seu argumento é um número. É seu trabalho certificar-se de que é (ou adicionar um teste adequado).

    • Em geral, não há tratamento de erros no exemplo, então talvez você queira adicionar alguns. Há return 1quando a função não pode obter um caminho para o tty recém-criado via fifo; ainda este status de saída da função não é tratado no código principal. Corrija isso e muito mais por conta própria. Em particular, você pode querer testar se o descritor desejado realmente leva a um tty ( [ -t 21 ]) antes de redirecionar qualquer coisa para ele.

    • create-logging-ttyusa a {variable}<>…sintaxe para criar um descritor de arquivo temporário, onde o shell escolhe um número não utilizado (10 ou maior) para ele e atribui o número ao arquivo variable. Para garantir que isso não leve o número solicitado puramente por acaso, a função cria primeiro um descritor de arquivo com o número solicitado, antes de saber o tty para o qual o descritor deve eventualmente apontar. Com efeito, você pode solicitar qualquer número sensato e os internos da função não colidirão com nada.

    • Se todo o seu script usa a {variable}<>…sintaxe ou similar, você pode não gostar da ideia de um número codificado como 21. Isso pode ser facilmente resolvido:

      exec {foo}>/dev/null
      create-logging-tty "$foo" ./log
      exec 1>&"$foo" 2>&"$foo"
      …
      destroy-logging-tty "$foo"
      
    • Inside unbuffer tty(comando) é usado para obter o caminho para tty fornecido por unbuffer. Relata formalmente ttyseu stdin, gostaríamos de saber seu stdout. Não importa porque ambos apontam para o mesmo tty.

    • Graças a flocknão há necessidade de matar unbufferou a casca que gera. É assim que funciona:

      1. save-stdout-stderrbloqueia o fifo que ele criou, ele usa um descritor aberto para o fifo para isso. Notas:
        • A função é executada no shell principal, então, na verdade, o descritor é aberto no shell principal e, portanto, o processo ( bash) interpretando todo o script mantém o bloqueio.
        • O bloqueio não impede que outros processos escrevam no fifo. Ele só os bloqueia quando eles querem bloquear o fifo para si mesmos. Isto é o que o código do shell em execução unbufferfará.
      2. O código shell rodando sob unbufferreporta seu tty através do fifo e então ele tenta bloquear o fifo usando seu próprio descritor de arquivo 3. O ponto é flockbloquear até obter o bloqueio.
      3. A função lê as informações sobre o tty, cria o descritor solicitado e bloqueia o tty usando o descritor. Só assim desbloqueia o fifo.
      4. O primeiro flockunder unbuffernão está mais bloqueado. A execução vai para o segundo flockque tenta travar o tty e os blocos.
      5. O roteiro principal continua. Quando o tty não é mais necessário, o shell principal o desbloqueia via destroy-logging-tty.
      6. Só então o segundo flocksob unbufferdesbloqueia. O shell lá sai (liberando seus bloqueios automaticamente), unbufferdestrói o tty e sai, teesai. Nenhuma manutenção é necessária.

      Se não bloquearmos o fifo, mas deixarmos o shell unbuffertravar o tty imediatamente, pode acontecer que ele obtenha o bloqueio antes do shell principal, então ele termina imediatamente. O shell principal não pode bloquear o tty antes de aprender o que é. Usando outro bloqueio e a sequência correta de bloqueio e desbloqueio, podemos ter certeza de que unbuffersai somente depois que o shell principal for concluído com o tty.

      A grande vantagem é: se o shell principal sair por qualquer motivo (incluindo SIGKILL) antes de ser executado destroy-logging-tty, o kernel liberará todos os bloqueios mantidos pelo processo de qualquer maneira. Isso significa unbufferque acabará por terminar, não haverá processo obsoleto.

    • Você pode se perguntar se ttyescrever no fifo pode bloquear até que a função leia a partir dele. Bom, basta abrir o fifo para leitura. Mesmo que o fifo nunca seja lido, um processo de escrita como o nosso ttypoderá escrever vários (mil) bytes nele antes de ser bloqueado. O fifo é aberto para leitura no shell principal, mas mesmo que saia prematuramente há o shell dentro do unubfferqual acaba de abrir o fifo para escrita e leitura. Isso não deve bloquear.

    • A única sobra pode ser o fifo e seu diretório, se o shell principal for encerrado em um momento infeliz. Você pode suprimir ou prender certos sinais até que o momento infeliz passe; ou você pode trap … EXITe limpar de dentro da armadilha. Ainda existem cenários (por exemplo, SIGKILL) em que seu script simplesmente não pode fazer nada.

    • Testei a solução com interativo mce basicamente funcionou. Espere que a saída ( ./log) de tais aplicativos contenha muitas sequências de controle. O log pode ser reproduzido, por exemplo pv -qL 400 log, em um terminal que não seja muito pequeno.

    • Em meus testes mcreagiu a SIGWINCHpartir de seu terminal de controle (ou seja, o terminal principal, não o de unbuffer) e redesenhou sua janela, mas usou o tamanho do terminal sendo sua saída (a de unbuffer) e nunca mudou.

      Mesmo se unbufferreagiu SIGWINCHou foi forçado a atualizar o tamanho, pode ser tarde demais, mcpode já ter lido as dimensões antigas. Parece que essa atualização não acontece de qualquer maneira. Uma solução simples é evitar redimensionar o terminal.


    Problema mais amplo

    O problema com mce redimensionamento é devido a uma questão mais ampla. Você escreveu:

    O que eu quero conseguir é executar um comando para que ele imprima sua saída no console diretamente […]

    A solução acima ou semelhante quando há outro tty cuja saída é registrada e impressa no console original certamente não é "diretamente". mcatualizaria corretamente seu tamanho se fosse impresso diretamente no terminal original.

    Normalmente você não pode imprimir diretamente em um terminal e registrar o que o terminal recebe, a menos que o próprio terminal suporte o registro. Pseudoterminais criados por screenou tmuxpodem fazer isso e você pode configurá-los programaticamente de dentro de um script. Alguns emuladores de terminal com GUI podem permitir que você despeje o que eles recebem, você precisa configurá-los via GUI. O ponto é que você precisa de um terminal com o recurso. Execute um script em um terminal "errado" e você não poderá logar dessa maneira (você pode usar reptyrpara "movê-lo" para outro terminal). O script pode redirecionar sua saída como nosso script, mas isso não é "diretamente". Ou…

    Existem maneiras de bisbilhotar um tty ( exemplos ). Talvez você encontre algo que atenda às suas necessidades. Normalmente, tal espionagem requer acesso elevado, mesmo se você quiser espionar em um tty que você possa ler e escrever.

    • 1

relate perguntas

  • Notificar-enviar notificações aparecendo na janela

  • execute o contêiner do docker como root

  • Como ativar o sensor de impressão digital no domínio e no diretório ativo do Linux

  • Como alterar permanentemente Ctrl + C para Ctrl + K no CentOS 7?

  • como abrir um arquivo de escritório do WSL

Sidebar

Stats

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

    Como posso reduzir o consumo do processo `vmmem`?

    • 11 respostas
  • Marko Smith

    Baixar vídeo do Microsoft Stream

    • 4 respostas
  • Marko Smith

    O Google Chrome DevTools falhou ao analisar o SourceMap: chrome-extension

    • 6 respostas
  • Marko Smith

    O visualizador de fotos do Windows não pode ser executado porque não há memória suficiente?

    • 5 respostas
  • Marko Smith

    Como faço para ativar o WindowsXP agora que o suporte acabou?

    • 6 respostas
  • Marko Smith

    Área de trabalho remota congelando intermitentemente

    • 7 respostas
  • Marko Smith

    O que significa ter uma máscara de sub-rede /32?

    • 6 respostas
  • Marko Smith

    Ponteiro do mouse movendo-se nas teclas de seta pressionadas no Windows?

    • 1 respostas
  • Marko Smith

    O VirtualBox falha ao iniciar com VERR_NEM_VM_CREATE_FAILED

    • 8 respostas
  • Marko Smith

    Os aplicativos não aparecem nas configurações de privacidade da câmera e do microfone no MacBook

    • 5 respostas
  • Martin Hope
    CiaranWelsh Como posso reduzir o consumo do processo `vmmem`? 2020-06-10 02:06:58 +0800 CST
  • Martin Hope
    Jim Pesquisa do Windows 10 não está carregando, mostrando janela em branco 2020-02-06 03:28:26 +0800 CST
  • Martin Hope
    v15 Por que uma conexão de Internet gigabit/s via cabo (coaxial) não oferece velocidades simétricas como fibra? 2020-01-25 08:53:31 +0800 CST
  • Martin Hope
    fixer1234 O "HTTPS Everywhere" ainda é relevante? 2019-10-27 18:06:25 +0800 CST
  • Martin Hope
    andre_ss6 Área de trabalho remota congelando intermitentemente 2019-09-11 12:56:40 +0800 CST
  • Martin Hope
    Riley Carney Por que colocar um ponto após o URL remove as informações de login? 2019-08-06 10:59:24 +0800 CST
  • Martin Hope
    zdimension Ponteiro do mouse movendo-se nas teclas de seta pressionadas no Windows? 2019-08-04 06:39:57 +0800 CST
  • Martin Hope
    jonsca Todos os meus complementos do Firefox foram desativados repentinamente, como posso reativá-los? 2019-05-04 17:58:52 +0800 CST
  • Martin Hope
    MCK É possível criar um código QR usando texto? 2019-04-02 06:32:14 +0800 CST
  • Martin Hope
    SoniEx2 Altere o nome da ramificação padrão do git init 2019-04-01 06:16:56 +0800 CST

Hot tag

windows-10 linux windows microsoft-excel networking ubuntu worksheet-function bash command-line hard-drive

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