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 / 760143
Accepted
Stéphane Chazelas
Stéphane Chazelas
Asked: 2023-10-29 23:20:48 +0800 CST2023-10-29 23:20:48 +0800 CST 2023-10-29 23:20:48 +0800 CST

Por que unbuffer -p altera sua entrada?

  • 772
$ seq 10 | unbuffer -p od -vtc
0000000   1  \n   2  \n   3  \n   4  \n   5  \n   6  \n   7  \n   8  \n

Para onde foi 9e 10foi?

$ printf '\r' | unbuffer -p od -An -w1 -vtc
  \n

Por que foi \ralterado para \n?

$ : | unbuffer -p printf '\n' | od -An -w1 -vtc
  \r
  \n
$ unbuffer -p printf '\n' | od -An -w1 -vtc
  \r
      \n

O que é isso?

$ printf foo | unbuffer -p cat
$

Por que nenhuma saída (e um atraso de um segundo)?

$ printf '\1\2\3foo bar\n'  | unbuffer -p od -An -w1 -vtc
$

Por que não há saída?

$ (printf '\23'; seq 10000) | unbuffer -p cat

Por que ele trava sem saída?

$ unbuffer -p sleep 10

Por que não consigo ver o que digito (e por que é descartado mesmo sleepsem ter lido)?

Aliás, também:

$ echo test | unbuffer -p grep foo && echo found foo
found foo

Como foi grepencontrado foo, mas não imprimiu as linhas que o contêm?

$ unbuffer -p ls /x 2> /dev/null
ls: cannot access '/x': No such file or directory

Por que o erro não foi para /dev/null?

Veja também Unbuffer convertendo todos os caracteres em sino?

$ echo ${(l[10000][foo])} | unbuffer -p cat | wc -c
4095

Isso é com:

$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description:    Debian GNU/Linux trixie/sid
Release:        n/a
Codename:       trixie
$ uname -rsm
Linux 6.5.0-3-amd64 x86_64
$ expect -c 'puts "expect [package require Expect] tcl [info patchlevel]"'
expect 5.45.4 tcl 8.6.13
$ /proc/self/exe --version
zsh 5.9 (x86_64-debian-linux-gnu)

O mesmo no Ubuntu 22.04 ou no FreeBSD 12.4-RELEASE-p5 (exceto que os odcomandos precisam ser adaptados lá, e eu recebo 2321 (todos os caracteres BEL lá) em vez de 4095 acima).

expect
  • 1 1 respostas
  • 1252 Views

1 respostas

  • Voted
  1. Best Answer
    Stéphane Chazelas
    2023-10-29T23:20:48+08:002023-10-29T23:20:48+08:00

    unbufferé uma ferramenta para desabilitar o buffer que alguns comandos fazem quando sua saída não vai para um dispositivo terminal.

    Quando a saída vai para um dispositivo terminal, os comandos assumem que há um usuário real olhando ativamente para a saída, então eles a enviam assim que ela estiver disponível. Bem, não exatamente, eles enviam com base em linhas, ou seja, enviam linhas concluídas assim que estiverem prontas para saída.

    Quando não vai para um dispositivo terminal, como quando stdout é um arquivo normal ou um pipe, como otimização, eles o enviam em blocos. Isso significa menos write()s e, no caso de um pipe, significa que o leitor do outro lado não precisa ser acordado com tanta frequência, o que significa menos mudanças de contexto.

    No entanto, isso significa que em:

    cmd | other-cmd
    

    executado em um terminal, onde other-cmdhá algum tipo de comando de filtragem/transformação, other-cmdo stdout de é com buffer de linha, mas cmdo de é com buffer completo, o que significa que o usuário interativo não vê a saída de cmd(conforme transformada por other-cmd) assim que pois está disponível, mas atrasado e em grandes lotes.

    unbuffer cmd | other-cmd
    

    Ajuda porque restaura um buffer baseado em linha, cmdmesmo que seu stdout esteja indo para um canal.

    Para isso, ele inicia cmdem um pseudoterminal e encaminha o que vem desse pseudoterminal para o pipe. Então cmdpensa que está conversando com um usuário novamente e faz o buffer de linha.

    unbufferna verdade está escrito em expect. É um exemplo de script no expectcódigo-fonte , geralmente incluído no expectpacote fornecido pelos sistemas operacionais.

    expecté uma ferramenta usada para realizar interações automáticas com aplicativos de terminal usando pseudo-terminais, de modo que unbuffero comando é trivial para escrever expect. Brincando, a seção BUGSunbuffer da página de manual do tem: A página de manual é mais longa que o programa. E, de fato, o programa é apenas:

    #!/bin/sh
    # -*- tcl -*-
    # The next line is executed by /bin/sh, but not tcl \
    exec tclsh8.6 "$0" ${1+"$@"}
    
    package require Expect
    
    
    # -*- tcl -*-
    # Description: unbuffer stdout of a program
    # Author: Don Libes, NIST
    
    if {[string compare [lindex $argv 0] "-p"] == 0} {
        # pipeline
        set stty_init "-echo"
        eval [list spawn -noecho] [lrange $argv 1 end]
        close_on_eof -i $user_spawn_id 0
        interact {
            eof {
                # flush remaining output from child
                expect -timeout 1 -re .+
                return
            }
        }
    } else {
        set stty_init "-opost"
        set timeout -1
        eval [list spawn -noecho] $argv
        expect
        exit [lindex [wait] 3]
    }
    

    Como você pode ver e confirmado pela página de manual, unbuffertambém suporta uma -popção.

    No unbuffer cmd, o pseudoterminal não está conectado apenas ao stdout do cmd, mas também ao seu stdin e stderr (lembre-se que expecté uma ferramenta destinada a interagir com comandos):

    $ tty; unbuffer readlink /proc/self/fd/{0..2}
    /dev/pts/14
    /dev/pts/15
    /dev/pts/15
    /dev/pts/15
    

    Isso explica por que unbuffer ls /x 2> /dev/nullnão enviou os erros para /dev/null, o stderr foi mesclado com o stdout.

    Agora, unbuffernão lê nada do seu próprio stdin e não envia nada para o stdin do cmd.

    Isso significa que A | unbuffer cmd | Bnão vai funcionar.

    É aí que entra a opção -p(for pipe). Como visto no código, with -p, unbufferusa interactem vez de expectcomo o loop ativo que processa os dados provenientes dos diferentes canais.

    Somente com a expectinstrução, expect(o programa/biblioteca TCL) lê o que vem do pseudoterminal (ou seja, o que cmdescreve no lado escravo por meio de seu stdout ou stderr, por exemplo) e apenas envia para seu próprio stdout.

    Com interact, expectfaz isso, mas também:

    • envia o que lê de sua própria entrada padrão para o pseudo terminal (para que cmdpossa ler lá)
    • e também, se unbufferstdin for um dispositivo terminal, interactcoloca-o no rawmodo com local echodesabilitado.

    Isso é bom porque a saída de in A | unbuffer -p cmd | B, pode ser lida como entrada , mas significa algumas coisas:Acmd

    • unbufferconfigura o pseudoterminal interno com set stty_init "-echo", mas não no rawmodo. Em particular, isig(o tratamento de ^C( \3) / ^Z/ ^\), ixon(controle de fluxo, ^Q/ ^S( \23)) não estão desativados. Quando a entrada é um dispositivo terminal (que é como expect's interactdeve ser usado, mas não unbuffer), tudo bem, pois o host é colocado em rawmodo, o que significa apenas que o processamento é movido do terminal host para o pseudo-incorporado. terminal, exceto pelo fato de echoestar desabilitado em ambos para que você não possa ver o que digita. Mas quando não é um dispositivo terminal, isso significa que, por exemplo, qualquer byte 0x3 ( ^C) na entrada (como ao processar a saída deprintf '\3') aciona um SIGINT e finaliza o comando, qualquer byte 0x19 ( printf '\23') interrompe o fluxo. icrnlnão estar desabilitado explica por que \r's foram alterados para \n's.

    • Ele não faz o stty -opostque faria sem -p. Isso explica por que a \nsaída de by cmdfoi alterada para \r\n. E quando a entrada é um dispositivo terminal, o fato de colocá-lo em raw, portanto, com opostdesativado explica a saída do terminal desconfigurada quando os caracteres de nova linha gerados por odnão são transformados em \r\n.

    • o pseudo-terminal interno ainda tem o editor de linha habilitado, então nada será enviado cmda menos que haja um caractere \ror \nvindo da entrada, o que explica por que printf foo | unbuffer -p catnão imprime nada.

      E como esse editor de linha tem um limite no tamanho da linha que pode editar ( 4095 no meu sistema (Linux) , um quinto da velocidade tty ¹ no FreeBSD), você acaba com o tipo de problema no Unbuffer ao converter todos os caracteres tocar a campainha? : acontece a mesma coisa que quando você tenta inserir uma linha muito longa no teclado em um aplicativo idiota como cat. No Linux, todos os caracteres após o 4094 são ignorados, mas \nsão aceitos e submetem a linha; no FreeBSD, após a inserção de 38400/5 caracteres, qualquer extra é recusado (mesmo \n) e faz com que um BEL seja enviado ao terminal². O que explica por que você obtém 2.321 BELs lá (10.001 - 38.400/5).

    • O manuseio de EOF é complicado com dispositivos pseudoterminais. Quando EOF é visto no unbufferstdin, ele não pode encaminhar essas informações para cmd. Então in seq 10 | od -vtc, depois seqde terminar, odainda está aguardando mais entradas do pseudoterminal que nunca chegarão. Em vez disso, nesse ponto, tudo é demolido e odeliminado (a página de manual menciona essa limitação).

    Para seu próprio propósito, seria muito melhor unbuffercolocar o pseudo-tty incorporado no raw -echomodo e deixar o dispositivo terminal host (se houver) sozinho. No entanto expect, não suporta realmente esse modo de operação, não foi projetado para isso.

    Agora, se unbufferse trata de remover stdout do buffer, não há razão para que ele toque em stdin e stderr.

    Na verdade, podemos contornar isso fazendo:

    unbuffer() {
      command unbuffer sh -c 4<&0 5>&2 '
        exec <&4 4<&- 2>&5 5>&- "$@"' sh "$@"
    }
    

    Isso é usado shpara restaurar o stdin e o stderr originais (transmitidos pelo shell de chamada via fds 4 e 5; não usando fd 3 como expectacontece com o uso explícito daquele internamente).

    Então:

    $ echo test | unbuffer readlink /proc/self/fd/{0..2} 2> /dev/null | cat
    pipe:[184479]
    /dev/pts/16
    /dev/null
    

    Apenas o stdout vai para o pseudo-terminal para ser removido do buffer.

    E todos os outros problemas desaparecem:

    $ unbuffer ls /x 2> /dev/null
    $ printf '\r'  | unbuffer od -An -w1 -vtc
      \r
    $ : | unbuffer printf '\n' | od -An -w1 -vtc
      \n
    $ unbuffer printf '\n' | od -An -w1 -vtc
      \n
    $ printf foo | unbuffer cat
    foo
    $ printf '\1\2\3foo bar\n' | unbuffer od -An -w1 -vtc
     001
     002
     003
       f
       o
       o
    
       b
       a
       r
      \n
    $ (printf '\23'; seq 10000) | unbuffer cat -vte | head
    ^S1$
    2$
    3$
    4$
    5$
    6$
    7$
    8$
    9$
    10$
    $ unbuffer sleep 10
    I see what I type
    $ I see what I type
    zsh: command not found: I
    $ echo test | unbuffer grep foo || echo not found
    not found
    $ echo ${(l[10000][foo])} | unbuffer cat | wc -c
    10001
    

    Além disso, a instalação expect(que requer um interpretador TCL) parece um pouco exagerada quando tudo o que você precisa é fazer com que o stdout passe cmdpor um pseudo-terminal.

    socattambém pode fazer isso:

    $ echo test | socat -u system:'readlink /proc/self/fd/[0-2]; wc -c',pty,raw - 2> /dev/null | cat
    pipe:[187759]
    /dev/pts/17
    /dev/null
    5
    

    (ele registra o status de saída de falha, mas por outro lado não propaga o status de saída do comando).

    O zshshell ainda possui suporte integrado para pseudo-ttys, e uma unbufferfunção pode ser escrita com pouco esforço com:

    zmodload zsh/zpty
    zmodload zsh/zselect
    unbuffer() {
      {
        return "$(
          exec 6>&1 >&5 5>&-
          # here fds go:
          #  0,3: orig stdin
          #    1: orig stdout
          #  2,4: orig stderr
          #    5: closed
          #    6: to return argument
          zpty -b unbuffer '
            stty raw
            exec <&3 3<&- 2>&4 4>&-
            # here fds go:
            #     0: orig stdin
            #     1: pseudo unbuffering tty
            #     2: orig stderr
            # 3,4,5: closed
            #     6: to return argument
            "$@" 6>&-
            echo "$?" >&6 
          '
          fd=$REPLY
          until
            zselect -r $fd
            zpty -r unbuffer
            (( $? == 2 ))
          do
            continue
          done
        )"
      } 3<&0 4>&2 5>&1
    }
    

    Cuidado, todos eles acabam rodando em um novo terminal e exceto pela socatabordagem (a menos que você use as opções cttye setid) em uma nova sessão. Portanto, agora, se esses "fixos" unbufferforem iniciados em segundo plano na sessão do terminal host, a cmdleitura do terminal host não será interrompida. Por exemplo, unbuffer cat&acabará com uma leitura de trabalho em segundo plano em seu terminal, causando estragos.


    ¹ Limitado a 65536. A velocidade de um pseudo-terminal é irrelevante, mas deve haver um anunciado e acho que é 38400 por padrão no sistema FreeBSD em que testei. Como a velocidade é copiada daquela do terminal que controla expect, pode-se fazer um stty speed 115200(o valor máximo AFAICT) antes de chamar unbufferpara ampliar esse buffer. Mas você pode descobrir que ainda não obteve a linha grande completa de 10.000 caracteres. Isso é explicado no código do driver . Você encontrará unbuffer -p catretornos de apenas 4.096 bytes porque isso é o máximo catsolicitado em sua primeira read()chamada, e o driver tty retornou a mesma quantidade da linha de entrada, mas descartou o restante (!). Se você substituir porunbuffer -p dd bs=65536, você obterá a linha completa (bem, até 115200/5 bytes).

    ² você pode evitar esses BELs substituindo set stty_init "-echo"por set stty_init "-echo -imaxbel"no unbufferscript, mas isso não ajudará você a obter os dados.

    • 24

relate perguntas

  • esperar - como sair com o código de status 1 se um comando falhar?

  • Como fazer um script de espera para esperar um dos vários textos

  • O script esperado está retornando erros ao gerar o script bash. Por quê? [duplicado]

  • Esperar, Comando, Pipes e Gzip

  • Definir número de entrada com expect

Sidebar

Stats

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

    Possível firmware ausente /lib/firmware/i915/* para o módulo i915

    • 3 respostas
  • Marko Smith

    Falha ao buscar o repositório de backports jessie

    • 4 respostas
  • Marko Smith

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

    • 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

    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
    user12345 Falha ao buscar o repositório de backports jessie 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl Por que a maioria dos exemplos do systemd contém WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • 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
    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

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