O Git produz saída em tempo real para um arquivo, mas não consigo ecoar em tempo real diretamente em um loop while

0

O que eu quero

Meu objetivo real de longo prazo é ignorar toda a saída de um processo git em outra função (para fazer uma barra de progresso - veja my older question para mais detalhes sobre isso).

O que eu tenho até agora

Eu pensei que meu problema foi resolvido após esta pergunta mas sai agora que apenas parcialmente.

Eu já descobri que git

  • produz saída apenas para stderr por design
  • Eu tive que usar a opção --progress de git para forçar git a realmente imprimir o progresso.

Agora, se eu correr

                                  #or also &> output.file
git clone --progress http://someRepo > output.file 2>&1

Eu posso ver todo o progresso, por exemplo rodando ao mesmo tempo em um segundo terminal

tail -f output.file

Eu obtenho

Cloning into 'someRepo' ...
remote: Counting objects: 2618, done.
remote: Compressing objects: 100% (14/14), done.
Receiving objects:   4% (106/2618), 42.11 MiB | 5.67 MiB/s

que é atualizado em tempo real até o final

Cloning into 'someRepo' ...
remote: Counting objects: 2618, done.
remote: Compressing objects: 100% (14/14), done.
remote: Total 2618 (delta 2), reused 12 (delta 1), pack-reused 2603
Receiving objects: 100% (2618/2618), 258.95 MiB | 5.71 MiB/s, Done.
Resolving differences: 100% (1058/1058), Done.
Checking connectivity ... Done.

Até agora tudo bem.

Meu problema

Agora, não quero mais colocar essa saída em um arquivo, mas sim ignorá-lo em uma função. Então eu tentei

                                  #or also |&
git clone --progress http://someRepo 2>&1 | {
    while read -r line
    do
        # echo is for testing only
        # later I want to pass this to another function
        # EDIT: I added bypassed in order to see what is bypassed
        echo "bypassed: ${line}"
    done
}

Mas rodando assim eu só recebo saída como resultado (EDIT: eu adicionei a bypassed: na linha echo para ver como ela é passada).

bypassed: Cloning into 'someRepo'
bypassed: remote: Counting objects: 2618, done.
remote: Compressing objects: 100% (14/14), done.

e somente no final quando o download termina eu recebo o resto de uma só vez pulando para esta saída (EDIT: eu adicionei o bypassed: na linha echo para ver como ele é passado). / p>

bypassed: Cloning into 'someRepo' ...
bypassed: remote: Counting objects: 2618, done.
remote: Compressing objects: 100% (14/14), done.
remote: Total 2618 (delta 2), reused 12 (delta 1), pack-reused 2603
Receiving objects: 100% (2618/2618), 258.95 MiB | 5.71 MiB/s, Done.
Resolving differences: 100% (1058/1058), Done.
bypassed: Checking connectivity ... Done.

Então, aparentemente, as 5 linhas do bloco de progresso são todas transmitidas de uma só vez. Talvez isso seja um problema para o pipe e o loop while?

Então, o que estou perdendo? Como eu tenho que executar o comando para obter a saída também "ecoado" / bypassed em minha função mais tarde, em tempo real, da mesma forma que o tail -f output.file é atualizado em tempo real?

EDITAR

Eu também já experimentei stdbuf e até unbuffer como sugerido aqui e aqui

stdbuf -i0 -o0 -e0 git clone --progress http://someRepo |& ...

stdbuf -oL git clone --progress http://someRepo |& ...

unbuffer git clone --progress http://someRepo |& ...

mas isso não alterou a saída.

    
por derHugo 28.12.2017 / 15:44

1 resposta

3

Esta é uma pergunta muito interessante e eu tive que experimentar por um tempo, mas a resposta é realmente super simples!

Como funciona a saída de progresso do git ? Ele mostra uma única linha de status com uma porcentagem de progresso e atualiza o tempo todo até que seja concluído. Ele faz isso não imprimindo um avanço de linha \n , mas um caractere retorno de carro \r no final da linha de progresso, o que faz com que o cursor de saída retorne novamente ao início da linha pronto para sobrescrever a última linha escrita novamente com um valor atualizado.

Você pode observar isso canalizando a saída de git através de cat -A , que não interpreta caracteres invisíveis, mas os exibe, por exemplo, \r torna-se ^M e as quebras de linha também são denotadas com $ :

Cloning into 'MyRepository'...$
remote: Counting objects: 2317, done.        $
Receiving objects:   0% (1/2317)   ^MReceiving objects:   1% (24/2317)   ^MReceiving objects:   2% (47/2317)   ^MReceiving objects:   3% (70/2317)   ^MReceiving objects:   4% (93/2317)   ^MReceiving objects:   5% (116/2317)   
[...]

Agora, por que isso afeta read ? É óbvio, como diz help read (extrair, ênfase minha):

Read a line from the standard input and split it into fields.

read espera por quebras de linha ( \n ) antes de terminar. A saída de git não continha nenhuma quebra de linha durante a exibição de progresso, portanto read não foi concluído. Somente depois que a exibição de progresso foi concluída e git realmente imprimiu a próxima linha, read consumiu toda a saída, incluindo todos os estados intermediários e retornos de carro, e você o repetiu dentro do seu loop. Se você canalizar a echo ou done a cat -A , verá a mesma saída cheia de ^M s, como acima.

O que você pode fazer para permitir que read capture todas as linhas de progresso intermediárias é traduzir todos os retornos de carro para quebras de linha reais, canalizando o material através de tr \r \n . Dessa forma, cada estado é impresso em uma nova linha, em vez de sobrescrever a linha anterior.

Então, o loop completo que você usou poderia ter a seguinte aparência:

git clone --progress http://someRepo 2>&1 | tr \r \n |
    while read -r line ; do
        echo "bypassed: ${line}"
    done

No meu sistema, a saída mostrada no terminal por essa solução ainda não é totalmente fluente e gagueja um pouco, mas não consegui melhorar ainda mais. Pelo menos agora você tem o resultado real "progresso".

    
por Byte Commander 28.12.2017 / 17:29