Como adicionar uma barra de progresso a um script de shell?


413

Ao criar scripts no bash ou em qualquer outro shell no * NIX, enquanto executa um comando que leva mais de alguns segundos, é necessária uma barra de progresso.

Por exemplo, copiando um arquivo grande, abrindo um arquivo tar grande.

De que maneiras você recomenda adicionar barras de progresso aos shell scripts?


Consulte também stackoverflow.com/questions/12498304/… para obter exemplos da lógica de controle (faça um plano de fundo de um trabalho e faça alguma coisa até terminar).
Tripleee

1
Há um conjunto de requisitos que frequentemente consideramos úteis ao criar scripts. registro, exibindo progresso, cores, resultados sofisticados etc ... Sempre achei que deveria haver algum tipo de estrutura de script simples. Finalmente, decidi implementar um, pois não encontrei nenhum. Você pode achar isso útil. É em pura festa, quero dizer Just Bash. github.com/SumuduLansakara/JustBash
Sumudu

Isso não deve ser movido para unix.stackexchange.com ?
Ethan

Respostas:


685

Você pode implementar isso substituindo uma linha. Use \rpara voltar ao início da linha sem escrever \nno terminal.

Escreva \nquando terminar para avançar na linha.

Use echo -nepara:

  1. não imprima \ne
  2. reconhecer seqüências de escape como \r.

Aqui está uma demonstração:

echo -ne '#####                     (33%)\r'
sleep 1
echo -ne '#############             (66%)\r'
sleep 1
echo -ne '#######################   (100%)\r'
echo -ne '\n'

Em um comentário abaixo, puk menciona isso "falha" se você começar com uma linha longa e depois escrever uma linha curta: Nesse caso, você precisará sobrescrever o comprimento da linha longa (por exemplo, com espaços).


23
De acordo com o eco página do homem (pelo menos no MacOS X) sh / bash usar seu próprio built-in echo comando que não aceita "-n" ... então, a fim de realizar a mesma coisa que você precisa colocar \ r \ c no final da cadeia, em vez de apenas \ r
Justin Jenkins

51
A maneira portátil de produzir isso é usar em printfvez de echo.
Jens

9
para printf, teríamos que usar este formato:, printf "#### (50%%)\r"ele não funcionaria com aspas simples e o sinal de porcentagem precisa ser escapado.
Nurettin

7
Eu não entendo isso - aceito e montes de upvotes para um hack "Vou adivinhar quanto tempo essa operação levará a hardware desconhecido"? pv é a resposta correta IMO (mas bar vai fazer também)
Stephen

19
A pergunta era "Como progredir as barras" com um exemplo de cópia de arquivos. Eu me concentrei no problema dos "gráficos", não no cálculo de quão longe está a operação de cópia de um arquivo.
Mitch Haile

73

Você também pode estar interessado em como fazer um girador :

Posso fazer um spinner no Bash?

Certo!

i=1
sp="/-\|"
echo -n ' '
while true
do
    printf "\b${sp:i++%${#sp}:1}"
done

Cada vez que o loop itera, ele exibe o próximo caractere na string sp, contornando quando chega ao fim. (i é a posição do caractere atual a ser exibido e $ {# sp} é o comprimento da string sp).

A cadeia \ b é substituída por um caractere 'backspace'. Como alternativa, você pode jogar com \ r para voltar ao início da linha.

Se você quiser diminuir a velocidade, coloque um comando de suspensão dentro do loop (após a impressãof).

Um equivalente POSIX seria:

sp='/-\|'
printf ' '
while true; do
    printf '\b%.1s' "$sp"
    sp=${sp#?}${sp%???}
done

Se você já possui um loop que dá muito trabalho, pode chamar a seguinte função no início de cada iteração para atualizar o spinner:

sp="/-\|"
sc=0
spin() {
   printf "\b${sp:sc++:1}"
   ((sc==${#sp})) && sc=0
}
endspin() {
   printf "\r%s\n" "$@"
}

until work_done; do
   spin
   some_work ...
done
endspin

15
Versão muito mais curta, * totalmente portátil: while :;do for s in / - \\ \|; do printf "\r$s";sleep .1;done;done(*: sleeppode exigir ints em vez de decimais)
Adam Katz

1
@Daenyth. Obrigado. Por favor, onde devemos chamar o comando que precisamos observar é progresso usando o código anterior?
goro

@ goro: Na some_work ...linha acima; uma discussão mais detalhada que se baseia nesta resposta útil e o comentário útil de Adam Katz - com foco na conformidade com POSIX - podem ser encontrados aqui .
precisa saber é o seguinte

@AdamKatz: Essa é uma simplificação útil e portátil, mas, para combinar com a abordagem de Daenyth, o botão giratório deve basear-se em \bvez de \r, pois, caso contrário, funcionará apenas no início de uma linha: while :; do for c in / - \\ \|; do printf '%s\b' "$c"; sleep 1; done; done- ou, se o cursor for exibido por trás do botão giratório é indesejável:printf ' ' && while :; do for c in / - \\ \|; do printf '\b%s' "$c"; sleep 1; done; done
mklement0

1
@kaushal - Ctrl + C o interromperá manualmente. Se você tem um trabalho em segundo plano, pode armazenar seu PID ( job=$!) e, em seguida while kill -0 $job 2>/dev/null;do …, executar , por exemplo:sleep 15 & job=$!; while kill -0 $job 2>/dev/null; do for s in / - \\ \|; do printf "\r$s"; sleep .1; done; done
Adam Katz

48

Algumas postagens mostraram como exibir o progresso do comando. Para calculá-lo, você precisará ver quanto progrediu. Nos sistemas BSD, alguns comandos, como dd (1), aceitam um SIGINFOsinal e relatam seu progresso. Nos sistemas Linux, alguns comandos respondem da mesma forma SIGUSR1. Se esse recurso estiver disponível, você poderá canalizar sua entrada ddpara monitorar o número de bytes processados.

Como alternativa, você pode usar lsofpara obter o deslocamento do ponteiro de leitura do arquivo e, assim, calcular o progresso. Eu escrevi um comando, chamado pmonitor , que exibe o progresso do processamento de um processo ou arquivo especificado. Com ele, você pode fazer coisas como as seguintes.

$ pmonitor -c gzip
/home/dds/data/mysql-2015-04-01.sql.gz 58.06%

Uma versão anterior dos scripts de shell do Linux e FreeBSD aparece no meu blog .


Isso é incrível, eu sempre esqueço de canalizar as coisas através de pv :-) Eu acho que meu comando "stat" funciona um pouco diferente, minha versão (Linux) desse script: gist.github.com/unhammer/b0ab6a6aa8e1eeaf236b
unhammer

Ótimo post, sempre adore quando estiver awkem jogo!
ShellFish 29/05

Isso é ótimo! Obrigado pelo script incrível!
thebeagle

47

Tenho uma função de barra de progresso fácil que escrevi no outro dia:

#!/bin/bash
# 1. Create ProgressBar function
# 1.1 Input is currentState($1) and totalState($2)
function ProgressBar {
# Process data
    let _progress=(${1}*100/${2}*100)/100
    let _done=(${_progress}*4)/10
    let _left=40-$_done
# Build progressbar string lengths
    _fill=$(printf "%${_done}s")
    _empty=$(printf "%${_left}s")

# 1.2 Build progressbar strings and print the ProgressBar line
# 1.2.1 Output example:                           
# 1.2.1.1 Progress : [########################################] 100%
printf "\rProgress : [${_fill// /#}${_empty// /-}] ${_progress}%%"

}

# Variables
_start=1

# This accounts as the "totalState" variable for the ProgressBar function
_end=100

# Proof of concept
for number in $(seq ${_start} ${_end})
do
    sleep 0.1
    ProgressBar ${number} ${_end}
done
printf '\nFinished!\n'

Ou então,
https://github.com/fearside/ProgressBar/


você pode explicar a linha sob 1.2.1.1, por favor? Você está executando uma substituição sed com as variáveis ​​_fill e _empty? Estou confuso.
precisa

Em vez de usar o sed, estou usando o bash "Substituição de Substring" interno, pois esse é um trabalho fácil, prefiro usar as funções internas do bash para esse tipo de trabalho. O código parece melhor também. :-) Verifique aqui tldp.org/LDP/abs/html/string-manipulation.html e procure a substituição de substring.
fearside

e $ {_ fill} é atribuído como $ {_ done} número de espaços. Isso é lindo. Ótimo trabalho, cara. Eu definitivamente vou usar isso em todos os meus scripts bash haha
Chirag

Bom trabalho @fearside! Fiz um pequeno ajuste para pular quando _progress não mudou do último valor, para melhorar a velocidade. github.com/enobufs/bash-tools/blob/master/bin/progbar
enobufs

Doce. Alterar o traço pelo retângulo confere uma aparência mais profissional:printf "\rProgress : [${_fill// /▇}${_empty// / }] ${_progress}%%"
Mehdi LAMRANI 27/11/19

44

use o comando linux pv:

http://linux.die.net/man/1/pv

ele não sabe o tamanho se estiver no meio do fluxo, mas fornece velocidade e total e, a partir daí, você pode descobrir quanto tempo deve levar e obter feedback para saber que não parou.


32

Eu estava procurando por algo mais sexy que a resposta selecionada, assim como meu próprio script.

Pré-visualização

progress-bar.sh em ação

Fonte

Eu coloquei no githubprogress-bar.sh

progress-bar() {
  local duration=${1}


    already_done() { for ((done=0; done<$elapsed; done++)); do printf "▇"; done }
    remaining() { for ((remain=$elapsed; remain<$duration; remain++)); do printf " "; done }
    percentage() { printf "| %s%%" $(( (($elapsed)*100)/($duration)*100/100 )); }
    clean_line() { printf "\r"; }

  for (( elapsed=1; elapsed<=$duration; elapsed++ )); do
      already_done; remaining; percentage
      sleep 1
      clean_line
  done
  clean_line
}

Uso

 progress-bar 100

1
Não entendo como isso é integrado a algum processamento em que a duração do processo não é conhecida. Como parar a barra de progresso se meu processo foi concluído anteriormente, por exemplo, para descompactar um arquivo.
jan

Eu acho que o uso deve serprogress-bar 100
jirarium

Progresso atraente de fato. Como ele pode ser vinculado a uma função que processa ação prolongada em servidores remotos através do ssh? Quero dizer, como é possível medir o tempo de uma atualização (por exemplo) em servidores remotos?
sem rosto

1
@faceless não está no escopo deste código que você fornecer o tempo e contagem regressiva
Édouard Lopez

1
@Fusion é um caractere unicode (U + 2587 LOVE SETE EIGHTHS BLOCK) que deve ser seguro para shell moderno. Experimentá-lo em seus envs
Édouard Lopez

18

Alcatrão GNU tem uma opção útil que fornece a funcionalidade de uma barra de progresso simples.

(...) Outra ação de ponto de verificação disponível é 'ponto' (ou '.'). Ele instrui o tar a imprimir um único ponto no fluxo de listagem padrão, por exemplo:

$ tar -c --checkpoint=1000 --checkpoint-action=dot /var
...

O mesmo efeito pode ser obtido por:

$ tar -c --checkpoint=.1000 /var

+1 para a abordagem mais simples! Se você não vir nenhum ponto impresso, tente diminuir o número, por exemplo --checkpoint=.10. Também funciona muito bem ao extrair com tar -xz.
Noam Manos

13

Um método mais simples que funciona no meu sistema usando o utilitário pipeview (pv).

srcdir=$1
outfile=$2


tar -Ocf - $srcdir | pv -i 1 -w 50 -berps `du -bs $srcdir | awk '{print $1}'` | 7za a -si $outfile

13

Não vi nada parecido e todas as funções personalizadas aqui parecem se concentrar apenas na renderização, então ... minha solução muito simples compatível com POSIX abaixo, com explicações passo a passo, porque esta pergunta não é trivial.

TL; DR

Renderizar a barra de progresso é muito fácil. Estimar quanto dele deve render é uma questão diferente. Eis como renderizar (animar) a barra de progresso - você pode copiar e colar este exemplo em um arquivo e executá-lo:

#!/bin/sh

BAR='####################'   # this is full bar, e.g. 20 chars

for i in {1..20}; do
    echo -ne "\r${BAR:0:$i}" # print $i chars of $BAR from 0 position
    sleep .1                 # wait 100ms between "frames"
done
  • {1..20} - valores de 1 a 20
  • echo -n - imprima sem nova linha no final
  • echo -e - interpretar caracteres especiais durante a impressão
  • "\r" - retorno de carro, um caractere especial para retornar ao início da linha

Você pode fazer com que qualquer conteúdo seja processado a qualquer velocidade, para que esse método seja muito universal, por exemplo, frequentemente usado para visualização de "hackers" em filmes tolos, sem brincadeira.

Resposta completa

A questão do problema é como determinar o $ivalor, ou seja, quanto da barra de progresso deve ser exibida. No exemplo acima, deixei que ele aumentasse em forloop para ilustrar o princípio, mas um aplicativo da vida real usaria um loop infinito e calcularia a $ivariável em cada iteração. Para fazer esse cálculo, ele precisa dos seguintes ingredientes:

  1. quanto trabalho há para ser feito
  2. quanto trabalho foi feito até agora

Caso cpprecise do tamanho de um arquivo de origem e do tamanho do arquivo de destino:

#!/bin/sh

$src=/path/to/source/file
$tgt=/path/to/target/file

cp "$src" "$tgt" &                     # the & forks the `cp` process so the rest
                                       # of the code runs without waiting (async)

BAR='####################'

src_size=$(stat -c%s "$src")           # how much there is to do

while true; do
    tgt_size=$(stat -c%s "$tgt")       # how much has been done so far
    i=$(( $tgt_size * 20 / $src_size ))
    echo -ne "\r${BAR:0:$i}"
    if [ $tgt_size == $src_size ]; then
        echo ""                        # add a new line at the end
        break;                         # break the loop
    fi
    sleep .1
done
  • stat - verificar estatísticas do arquivo
  • -c - retornar valor formatado
  • %s - tamanho total

No caso de operações como descompactação de arquivo, calcular o tamanho da fonte é um pouco mais difícil, mas ainda tão fácil quanto obter o tamanho de um arquivo descompactado:

#!/bin/sh
src_size=$(gzip -l "$src" | tail -n1 | tr -s ' ' | cut -d' ' -f3)
  • gzip -l - exibir informações sobre o arquivo zip
  • tail -n1 - trabalhe com 1 linha a partir do fundo
  • tr -s ' ' - traduza vários espaços para um (aperte-os)
  • cut -d' ' -f3 - cortar a terceira coluna delimitada por espaço

Aqui está a carne do problema, no entanto. Esta solução é cada vez menos geral. Todos os cálculos do progresso real estão fortemente vinculados ao domínio que você está tentando visualizar: é uma operação de arquivo único, uma contagem regressiva do temporizador, um número crescente de arquivos em um diretório, operação em vários arquivos etc. não pode ser reutilizado. A única parte reutilizável é a renderização da barra de progresso. Para reutilizá-lo, você precisa abstraí-lo e salvá-lo em um arquivo (por exemplo /usr/lib/progress_bar.sh) e, em seguida, definir funções que calculam valores de entrada específicos para seu domínio. É assim que um código generalizado pode parecer (eu também fiz o$BAR dinamizei porque as pessoas estavam pedindo por ele, o resto deve estar claro agora):

#!/bin/sh

BAR_length=50
BAR_character='#'
BAR=$(printf "%${BAR_length}s" | tr ' ' $BAR_character)

work_todo=$(get_work_todo)             # how much there is to do

while true; do
    work_done=$(get_work_done)         # how much has been done so far
    i=$(( $work_done * $BAR_length / $work_todo ))
    echo -ne "\r${BAR:0:$i}"
    if [ $work_done == $work_todo ]; then
        echo ""
        break;
    fi
    sleep .1
done
  • printf - um built-in para imprimir coisas em um determinado formato
  • printf '%50s' - imprima nada, cubra-o com 50 espaços
  • tr ' ' '#' - traduza todo espaço para sinal de hash

E é assim que você usaria:

#!/bin/sh

src=/path/to/source/file
tgt=/path/to/target/file

function get_work_todo() {
    echo $(stat -c%s "$src")
}

function get_work_done() {
    [ -e "$tgt" ] &&                   # if target file exists
        echo $(stat -c%s "$tgt") ||    # echo its size, else
        echo 0                         # echo zero
}

cp "$src" "$tgt" &                     # copy in the background

source /usr/lib/progress_bar.sh        # execute the progress bar

Obviamente, ele pode ser envolvido em uma função, reescrita para trabalhar com fluxos canalizados, reescrita para outra linguagem, qualquer que seja o seu veneno.


1
Para aqueles que querem as coisas mais simples, acabei de fazer a minha com a primeira resposta do cprn. É uma barra de progresso muito simples em uma função que usa alguma regra estúpida de proporcionalidade para desenhar a barra: pastebin.com/9imhRLYX
YCN-


9

Barra de progresso do estilo APT (não interrompe a saída normal)

insira a descrição da imagem aqui

EDIT: Para uma versão atualizada, verifique minha página do github

Eu não estava satisfeito com as respostas sobre esta questão. O que eu procurava pessoalmente era uma barra de progresso sofisticada, como é visto pelo APT.

Eu dei uma olhada no código-fonte C do APT e decidi escrever meu próprio equivalente para o bash.

Essa barra de progresso permanecerá bem na parte inferior do terminal e não interferirá em nenhuma saída enviada ao terminal.

Observe que a barra está atualmente fixa em 100 caracteres. Se você deseja dimensioná-lo para o tamanho do terminal, isso também é bastante fácil de realizar (a versão atualizada na minha página do github lida com isso).

Vou postar meu script aqui. Exemplo de uso:

source ./progress_bar.sh
echo "This is some output"
setup_scroll_area
sleep 1
echo "This is some output 2"
draw_progress_bar 10
sleep 1
echo "This is some output 3"
draw_progress_bar 50
sleep 1
echo "This is some output 4"
draw_progress_bar 90
sleep 1
echo "This is some output 5"
destroy_scroll_area

O script (eu recomendo fortemente a versão no meu github):

#!/bin/bash

# This code was inspired by the open source C code of the APT progress bar
# http://bazaar.launchpad.net/~ubuntu-branches/ubuntu/trusty/apt/trusty/view/head:/apt-pkg/install-progress.cc#L233

#
# Usage:
# Source this script
# setup_scroll_area
# draw_progress_bar 10
# draw_progress_bar 90
# destroy_scroll_area
#


CODE_SAVE_CURSOR="\033[s"
CODE_RESTORE_CURSOR="\033[u"
CODE_CURSOR_IN_SCROLL_AREA="\033[1A"
COLOR_FG="\e[30m"
COLOR_BG="\e[42m"
RESTORE_FG="\e[39m"
RESTORE_BG="\e[49m"

function setup_scroll_area() {
    lines=$(tput lines)
    let lines=$lines-1
    # Scroll down a bit to avoid visual glitch when the screen area shrinks by one row
    echo -en "\n"

    # Save cursor
    echo -en "$CODE_SAVE_CURSOR"
    # Set scroll region (this will place the cursor in the top left)
    echo -en "\033[0;${lines}r"

    # Restore cursor but ensure its inside the scrolling area
    echo -en "$CODE_RESTORE_CURSOR"
    echo -en "$CODE_CURSOR_IN_SCROLL_AREA"

    # Start empty progress bar
    draw_progress_bar 0
}

function destroy_scroll_area() {
    lines=$(tput lines)
    # Save cursor
    echo -en "$CODE_SAVE_CURSOR"
    # Set scroll region (this will place the cursor in the top left)
    echo -en "\033[0;${lines}r"

    # Restore cursor but ensure its inside the scrolling area
    echo -en "$CODE_RESTORE_CURSOR"
    echo -en "$CODE_CURSOR_IN_SCROLL_AREA"

    # We are done so clear the scroll bar
    clear_progress_bar

    # Scroll down a bit to avoid visual glitch when the screen area grows by one row
    echo -en "\n\n"
}

function draw_progress_bar() {
    percentage=$1
    lines=$(tput lines)
    let lines=$lines
    # Save cursor
    echo -en "$CODE_SAVE_CURSOR"

    # Move cursor position to last row
    echo -en "\033[${lines};0f"

    # Clear progress bar
    tput el

    # Draw progress bar
    print_bar_text $percentage

    # Restore cursor position
    echo -en "$CODE_RESTORE_CURSOR"
}

function clear_progress_bar() {
    lines=$(tput lines)
    let lines=$lines
    # Save cursor
    echo -en "$CODE_SAVE_CURSOR"

    # Move cursor position to last row
    echo -en "\033[${lines};0f"

    # clear progress bar
    tput el

    # Restore cursor position
    echo -en "$CODE_RESTORE_CURSOR"
}

function print_bar_text() {
    local percentage=$1

    # Prepare progress bar
    let remainder=100-$percentage
    progress_bar=$(echo -ne "["; echo -en "${COLOR_FG}${COLOR_BG}"; printf_new "#" $percentage; echo -en "${RESTORE_FG}${RESTORE_BG}"; printf_new "." $remainder; echo -ne "]");

    # Print progress bar
    if [ $1 -gt 99 ]
    then
        echo -ne "${progress_bar}"
    else
        echo -ne "${progress_bar}"
    fi
}

printf_new() {
    str=$1
    num=$2
    v=$(printf "%-${num}s" "$str")
    echo -ne "${v// /$str}"
}

7

Isso permite visualizar que um comando ainda está sendo executado:

while :;do echo -n .;sleep 1;done &
trap "kill $!" EXIT  #Die with parent if we die prematurely
tar zxf packages.tar.gz; # or any other command here
kill $! && trap " " EXIT #Kill the loop and unset the trap or else the pid might get reassigned and we might end up killing a completely different process

Isso criará um loop while infinito que é executado em segundo plano e ecoa um "." todo segundo. Isso será exibido .no shell. Execute o tarcomando ou qualquer comando que desejar. Quando esse comando terminar de executar, mate o último trabalho em execução em segundo plano - que é o loop while infinito .


Não foi possível iniciar outro trabalho em segundo plano durante a execução e potencialmente ser morto em vez do loop de progresso?
Centimane

Eu acho que a idéia é que você colocaria isso em um script, então isso só trava uma saída desse script.
Iguananaut

1
Eu amo esse comando, estou usando-o nos meus arquivos. Estou um pouco desconfortável, porque realmente não entendo como isso funciona. A primeira e a terceira linhas são mais fáceis de entender, mas ainda não tenho certeza. Eu sei que esta é uma resposta de idade, mas há uma maneira eu posso obter uma explicação diferente voltada para iniciantes em programação de
Felipe

1
Esta é a ÚNICA resposta verdadeira, onde outros são apenas barras de progresso de brinquedos que não significam nada e não servem para programas reais, únicos e não rastreáveis ​​(quase TODOS). Obrigado.
bekce 23/07/19

@Felipe, O loop while é um processo em segundo plano. O $! na primeira interceptação captura a identificação do processo em segundo plano e garante que, se o processo atual / pai terminar, o processo em segundo plano também morra e não fique parado. A instrução kill apenas termina o processo em segundo plano quando seu comando ou comandos longos terminam.
floydn

7

Aqui está como pode parecer

Fazendo upload de um arquivo

[##################################################] 100% (137921 / 137921 bytes)

Aguardando a conclusão de um trabalho

[#########################                         ] 50% (15 / 30 seconds)

Função simples que o implementa

Você pode simplesmente copiar e colar no seu script. Não requer mais nada para funcionar.

PROGRESS_BAR_WIDTH=50  # progress bar length in characters

draw_progress_bar() {
  # Arguments: current value, max value, unit of measurement (optional)
  local __value=$1
  local __max=$2
  local __unit=${3:-""}  # if unit is not supplied, do not display it

  # Calculate percentage
  if (( $__max < 1 )); then __max=1; fi  # anti zero division protection
  local __percentage=$(( 100 - ($__max*100 - $__value*100) / $__max ))

  # Rescale the bar according to the progress bar width
  local __num_bar=$(( $__percentage * $PROGRESS_BAR_WIDTH / 100 ))

  # Draw progress bar
  printf "["
  for b in $(seq 1 $__num_bar); do printf "#"; done
  for s in $(seq 1 $(( $PROGRESS_BAR_WIDTH - $__num_bar ))); do printf " "; done
  printf "] $__percentage%% ($__value / $__max $__unit)\r"
}

Exemplo de uso

Aqui, fazemos upload de um arquivo e redesenhamos a barra de progresso a cada iteração. Não importa qual trabalho é realmente executado, desde que possamos obter 2 valores: valor máximo e valor atual.

No exemplo abaixo, o valor máximo é file_sizee o valor atual é fornecido por alguma função e é chamado uploaded_bytes.

# Uploading a file
file_size=137921

while true; do
  # Get current value of uploaded bytes
  uploaded_bytes=$(some_function_that_reports_progress)

  # Draw a progress bar
  draw_progress_bar $uploaded_bytes $file_size "bytes"

  # Check if we reached 100%
  if [ $uploaded_bytes == $file_size ]; then break; fi
  sleep 1  # Wait before redrawing
done
# Go to the newline at the end of upload
printf "\n"

Função pura e simples. Muito obrigado!
Andreas Kraft

É isso que estou procurando! Muito obrigado :)
wajdi_jurry

4

A maioria dos comandos unix não fornece o tipo de feedback direto a partir do qual você pode fazer isso. Alguns fornecerão a saída no stdout ou stderr que você pode usar.

Para algo como tar, você pode usar a opção -v e canalizar a saída para um programa que atualiza uma pequena animação para cada linha que lê. À medida que o tar escreve uma lista de arquivos, é revelado que o programa pode atualizar a animação. Para completar uma porcentagem, você precisa saber o número de arquivos e contar as linhas.

O cp não fornece esse tipo de saída até onde eu sei. Para monitorar o progresso do cp, é necessário monitorar os arquivos de origem e destino e observar o tamanho do destino. Você pode escrever um pequeno programa c usando a chamada de sistema stat (2) para obter o tamanho do arquivo. Isso leria o tamanho da fonte, pesquisaria o arquivo de destino e atualizaria uma barra% concluída com base no tamanho do arquivo gravado até a data.


4

Minha solução exibe a porcentagem do tarball que está sendo descompactado e gravado. Eu uso isso ao escrever imagens de sistema de arquivos raiz de 2 GB. Você realmente precisa de uma barra de progresso para essas coisas. O que eu faço é usar gzip --list para obter o tamanho total não compactado do tarball. A partir disso, calculo o fator de bloqueio necessário para dividir o arquivo em 100 partes. Por fim, imprimo uma mensagem de ponto de verificação para cada bloco. Para um arquivo de 2 GB, isso dá cerca de 10 MB por bloco. Se isso for muito grande, você poderá dividir o BLOCKING_FACTOR por 10 ou 100, mas será mais difícil imprimir uma saída bonita em termos de porcentagem.

Supondo que você esteja usando o Bash, poderá usar a seguinte função shell

untar_progress () 
{ 
  TARBALL=$1
  BLOCKING_FACTOR=$(gzip --list ${TARBALL} |
    perl -MPOSIX -ane '$.==2 && print ceil $F[1]/50688')
  tar --blocking-factor=${BLOCKING_FACTOR} --checkpoint=1 \
    --checkpoint-action='ttyout=Wrote %u%  \r' -zxf ${TARBALL}
}

Solução agradável, mas como você faz quando deseja compactar um diretório?
Samir Sadek

4

Antes de tudo, a barra não é o único medidor de progresso de um tubo. O outro (talvez ainda mais conhecido) é o pv (visualizador de tubos).

Em segundo lugar, bar e pv podem ser usados, por exemplo, assim:

$ bar file1 | wc -l 
$ pv file1 | wc -l

ou até:

$ tail -n 100 file1 | bar | wc -l
$ tail -n 100 file1 | pv | wc -l

Um truque útil se você quiser usar os comandos bar e pv em comandos que estão trabalhando com arquivos fornecidos em argumentos, como, por exemplo, copiar arquivo1 arquivo2, é usar a substituição de processo :

$ copy <(bar file1) file2
$ copy <(pv file1) file2

A substituição de processo é uma coisa mágica do bash que cria arquivos temporários do canal fifo / dev / fd / e conecta o stdout do processo executado (entre parênteses) através desse canal e a cópia o vê como um arquivo comum (com uma exceção, ele só pode lê-lo para a frente).

Atualizar:

O próprio comando bar também permite copiar. Depois da barra de homem:

bar --in-file /dev/rmt/1cbn --out-file \
     tape-restore.tar --size 2.4g --buffer-size 64k

Mas, na minha opinião, a substituição de processos é uma maneira mais genérica de fazê-lo. E ele usa o próprio programa cp.


3

Eu prefiro usar o diálogo com o parâmetro --gauge . É usado com muita frequência em instalações de pacotes .deb e outros itens básicos de configuração de muitas distribuições. Então você não precisa reinventar a roda ... de novo

Basta colocar um valor int de 1 a 100 @stdin. Um exemplo básico e bobo:

for a in {1..100}; do sleep .1s; echo $a| dialog --gauge "waiting" 7 30; done

Eu tenho este arquivo / bin / Wait (com chmod u + x perms) para fins de culinária: P

#!/bin/bash
INIT=`/bin/date +%s`
NOW=$INIT
FUTURE=`/bin/date -d "$1" +%s`
[ $FUTURE -a $FUTURE -eq $FUTURE ] || exit
DIFF=`echo "$FUTURE - $INIT"|bc -l`

while [ $INIT -le $FUTURE -a $NOW -lt $FUTURE ]; do
    NOW=`/bin/date +%s`
    STEP=`echo "$NOW - $INIT"|bc -l`
    SLEFT=`echo "$FUTURE - $NOW"|bc -l`
    MLEFT=`echo "scale=2;$SLEFT/60"|bc -l`
    TEXT="$SLEFT seconds left ($MLEFT minutes)";
    TITLE="Waiting $1: $2"
    sleep 1s
    PTG=`echo "scale=0;$STEP * 100 / $DIFF"|bc -l`
    echo $PTG| dialog --title "$TITLE" --gauge "$TEXT" 7 72
done

if [ "$2" == "" ]; then msg="Espera terminada: $1";audio="Listo";
else msg=$2;audio=$2;fi 

/usr/bin/notify-send --icon=stock_appointment-reminder-excl "$msg"
espeak -v spanish "$audio"

Então eu posso colocar:

Wait "34 min" "warm up the oven"

ou

Wait "dec 31" "happy new year"


2

para mim, mais fácil de usar e mais bonito até agora é comando pvou barcomo um cara já escreveu

por exemplo: é necessário fazer um backup de toda a unidade com dd

normalmente você usa dd if="$input_drive_path" of="$output_file_path"

com pvvocê pode fazer assim:

dd if="$input_drive_path" | pv | dd of="$output_file_path"

e o progresso vai diretamente para STDOUTo seguinte:

    7.46GB 0:33:40 [3.78MB/s] [  <=>                                            ]

depois que é feito resumo aparece

    15654912+0 records in
    15654912+0 records out
    8015314944 bytes (8.0 GB) copied, 2020.49 s, 4.0 MB/s

Você pode usar pvou barvisualizar o progresso de diferentes processos, por exemplo, contagem regressiva do temporizador, posição em um arquivo de texto, instalação do aplicativo, configuração do tempo de execução, etc.?
Cprn 28/07/19

2

Muitas respostas descrevem a gravação de seus próprios comandos para impressão '\r' + $some_sort_of_progress_msg. O problema às vezes é que imprimir centenas dessas atualizações por segundo atrasará o processo.

No entanto, se algum de seus processos produzir saída (por exemplo 7z a -r newZipFile myFolder, produzirá cada nome de arquivo à medida que o compacta), existe uma solução mais simples, rápida, indolor e personalizável.

Instale o módulo python tqdm.

$ sudo pip install tqdm
$ # now have fun
$ 7z a -r -bd newZipFile myFolder | tqdm >> /dev/null
$ # if we know the expected total, we can have a bar!
$ 7z a -r -bd newZipFile myFolder | grep -o Compressing | tqdm --total $(find myFolder -type f | wc -l) >> /dev/null

Ajuda: tqdm -h. Um exemplo usando mais opções:

$ find / -name '*.py' -exec cat \{} \; | tqdm --unit loc --unit_scale True | wc -l

Como bônus, você também pode usar tqdmpara agrupar iterables no código python.

https://github.com/tqdm/tqdm/blob/master/README.rst#module


Não acho que seu exemplo com "mais opções" funcione. Parece passar o tqdmSTDOUT wc -latravés de um cano. Você provavelmente quer escapar disso.
Cprn 28/07/19

1
@cprn tqdmvai mostrar o progresso em STDERRenquanto tubulação sua entrada STDINpara STDOUT. Nesse caso, wc -lreceberia a mesma entrada como se tqdmnão estivesse incluída.
Casper.dcl 29/07/19

Ah, faz sentido agora. Obrigado por explicar.
cprn 30/07/19

2

Com base no trabalho de Edouard Lopez, criei uma barra de progresso que se ajusta ao tamanho da tela, seja ela qual for. Confira.

insira a descrição da imagem aqui

Também está publicado no Git Hub .

#!/bin/bash
#
# Progress bar by Adriano Pinaffo
# Available at https://github.com/adriano-pinaffo/progressbar.sh
# Inspired on work by Edouard Lopez (https://github.com/edouard-lopez/progress-bar.sh)
# Version 1.0
# Date April, 28th 2017

function error {
  echo "Usage: $0 [SECONDS]"
  case $1 in
    1) echo "Pass one argument only"
    exit 1
    ;;
    2) echo "Parameter must be a number"
    exit 2
    ;;
    *) echo "Unknown error"
    exit 999
  esac
}

[[ $# -ne 1 ]] && error 1
[[ $1 =~ ^[0-9]+$ ]] || error 2

duration=${1}
barsize=$((`tput cols` - 7))
unity=$(($barsize / $duration))
increment=$(($barsize%$duration))
skip=$(($duration/($duration-$increment)))
curr_bar=0
prev_bar=
for (( elapsed=1; elapsed<=$duration; elapsed++ ))
do
  # Elapsed
prev_bar=$curr_bar
  let curr_bar+=$unity
  [[ $increment -eq 0 ]] || {  
    [[ $skip -eq 1 ]] &&
      { [[ $(($elapsed%($duration/$increment))) -eq 0 ]] && let curr_bar++; } ||
    { [[ $(($elapsed%$skip)) -ne 0 ]] && let curr_bar++; }
  }
  [[ $elapsed -eq 1 && $increment -eq 1 && $skip -ne 1 ]] && let curr_bar++
  [[ $(($barsize-$curr_bar)) -eq 1 ]] && let curr_bar++
  [[ $curr_bar -lt $barsize ]] || curr_bar=$barsize
  for (( filled=0; filled<=$curr_bar; filled++ )); do
    printf "▇"
  done

  # Remaining
  for (( remain=$curr_bar; remain<$barsize; remain++ )); do
    printf " "
  done

  # Percentage
  printf "| %s%%" $(( ($elapsed*100)/$duration))

  # Return
  sleep 1
  printf "\r"
done
printf "\n"
exit 0

Aproveitar



1

Isso é aplicável apenas usando o zenity do gnome. O Zenity fornece uma ótima interface nativa para bash scripts: https://help.gnome.org/users/zenity/stable/

Exemplo de barra de progresso do Zenity:

#!/bin/sh
(
echo "10" ; sleep 1
echo "# Updating mail logs" ; sleep 1
echo "20" ; sleep 1
echo "# Resetting cron jobs" ; sleep 1
echo "50" ; sleep 1
echo "This line will just be ignored" ; sleep 1
echo "75" ; sleep 1
echo "# Rebooting system" ; sleep 1
echo "100" ; sleep 1
) |
zenity --progress \
  --title="Update System Logs" \
  --text="Scanning mail logs..." \
  --percentage=0

if [ "$?" = -1 ] ; then
        zenity --error \
          --text="Update canceled."
fi

1

Eu usei uma resposta de Criando seqüência de caracteres repetidos no shell script para repetição de caracteres. Eu tenho duas versões bash relativamente pequenas para scripts que precisam exibir a barra de progresso (por exemplo, um loop que passa por muitos arquivos, mas não é útil para arquivos tar grandes ou operações de cópia). O mais rápido consiste em duas funções, uma para preparar as strings para exibição de barra:

preparebar() {
# $1 - bar length
# $2 - bar char
    barlen=$1
    barspaces=$(printf "%*s" "$1")
    barchars=$(printf "%*s" "$1" | tr ' ' "$2")
}

e um para exibir uma barra de progresso:

progressbar() {
# $1 - number (-1 for clearing the bar)
# $2 - max number
    if [ $1 -eq -1 ]; then
        printf "\r  $barspaces\r"
    else
        barch=$(($1*barlen/$2))
        barsp=$((barlen-barch))
        printf "\r[%.${barch}s%.${barsp}s]\r" "$barchars" "$barspaces"
    fi
}

Pode ser usado como:

preparebar 50 "#"

o que significa preparar seqüências de caracteres para a barra com 50 caracteres "#" e depois:

progressbar 35 80

exibirá o número de caracteres "#" que corresponde à proporção 35/80:

[#####################                             ]

Esteja ciente de que a função exibe a barra na mesma linha repetidamente até que você (ou algum outro programa) imprima uma nova linha. Se você colocar -1 como primeiro parâmetro, a barra será apagada:

progressbar -1 80

A versão mais lenta é tudo em uma função:

progressbar() {
# $1 - number
# $2 - max number
# $3 - number of '#' characters
    if [ $1 -eq -1 ]; then
        printf "\r  %*s\r" "$3"
    else
        i=$(($1*$3/$2))
        j=$(($3-i))
        printf "\r[%*s" "$i" | tr ' ' '#'
        printf "%*s]\r" "$j"
    fi
}

e pode ser usado como (o mesmo exemplo acima):

progressbar 35 80 50

Se você precisar da barra de progresso no stderr, basta adicionar >&2no final de cada comando printf.


1

Para indicar o progresso da atividade, tente os seguintes comandos:

while true; do sleep 0.25 && echo -ne "\r\\" && sleep 0.25 && echo -ne "\r|" && sleep 0.25 && echo -ne "\r/" && sleep 0.25 && echo -ne "\r-"; done;

OU

while true; do sleep 0.25 && echo -ne "\rActivity: \\" && sleep 0.25 && echo -ne "\rActivity: |" && sleep 0.25 && echo -ne "\rActivity: /" && sleep 0.25 && echo -ne "\rActivity: -"; done;

OU

while true; do sleep 0.25 && echo -ne "\r" && sleep 0.25 && echo -ne "\r>" && sleep 0.25 && echo -ne "\r>>" && sleep 0.25 && echo -ne "\r>>>"; sleep 0.25 && echo -ne "\r>>>>"; done;

OU

while true; do sleep .25 && echo -ne "\r:Active:" && sleep .25 && echo -ne "\r:aCtive:" && sleep .25 && echo -ne "\r:acTive:" && sleep .25 && echo -ne "\r:actIve:" && sleep .25 && echo -ne "\r:actiVe:" && sleep .25 && echo -ne "\r:activE:"; done;

Pode-se usar sinalizadores / variáveis dentro do loop while para verificar e exibir o valor / extensão do progresso.


1

Usando as sugestões listadas acima, decidi implementar minha própria barra de progresso.

#!/usr/bin/env bash

main() {
  for (( i = 0; i <= 100; i=$i + 1)); do
    progress_bar "$i"
    sleep 0.1;
  done
  progress_bar "done"
  exit 0
}

progress_bar() {
  if [ "$1" == "done" ]; then
    spinner="X"
    percent_done="100"
    progress_message="Done!"
    new_line="\n"
  else
    spinner='/-\|'
    percent_done="${1:-0}"
    progress_message="$percent_done %"
  fi

  percent_none="$(( 100 - $percent_done ))"
  [ "$percent_done" -gt 0 ] && local done_bar="$(printf '#%.0s' $(seq -s ' ' 1 $percent_done))"
  [ "$percent_none" -gt 0 ] && local none_bar="$(printf '~%.0s' $(seq -s ' ' 1 $percent_none))"

  # print the progress bar to the screen
  printf "\r Progress: [%s%s] %s %s${new_line}" \
    "$done_bar" \
    "$none_bar" \
    "${spinner:x++%${#spinner}:1}" \
    "$progress_message"
}

main "$@"

1
Agradável! para fazê-lo funcionar eu tive que mudar a linha percent_none="$(( 100 - "$percent_done" ))"parapercent_none="$(( 100 - $percent_done))"
sergio

0

Eu fiz uma versão pura do shell para um sistema embarcado aproveitando:

  • Recurso de manipulação de sinal SIGUSR1 do / usr / bin / dd.

    Basicamente, se você enviar um 'kill SIGUSR1 $ (pid_of_running_dd_process)', ele exibirá um resumo da velocidade de transferência e da quantidade transferida.

  • fazendo o background do dd e consultando-o regularmente para atualizações, e gerando hash ticks como costumavam os clientes ftp da velha escola.

  • Usando / dev / stdout como destino para programas amigáveis ​​não stdout como scp

O resultado final permite que você faça qualquer operação de transferência de arquivos e obtenha atualizações de progresso que se parecem com a saída 'hash' do FTP da velha escola, onde você apenas obteria uma marca de hash para cada X bytes.

Isso dificilmente é um código de qualidade de produção, mas você entendeu. Eu acho fofo.

Pelo que vale, a contagem de bytes real pode não ser refletida corretamente no número de hashes - você pode ter um mais ou menos, dependendo dos problemas de arredondamento. Não use isso como parte de um script de teste, é apenas um colírio para os olhos. E, sim, sei que isso é terrivelmente ineficiente - é um script de shell e não peço desculpas por isso.

Exemplos com wget, scp e tftp fornecidos no final. Deve funcionar com qualquer coisa que emita dados. Certifique-se de usar / dev / stdout para programas que não são compatíveis com o stdout.

#!/bin/sh
#
# Copyright (C) Nathan Ramella (nar+progress-script@remix.net) 2010 
# LGPLv2 license
# If you use this, send me an email to say thanks and let me know what your product
# is so I can tell all my friends I'm a big man on the internet!

progress_filter() {

        local START=$(date +"%s")
        local SIZE=1
        local DURATION=1
        local BLKSZ=51200
        local TMPFILE=/tmp/tmpfile
        local PROGRESS=/tmp/tftp.progress
        local BYTES_LAST_CYCLE=0
        local BYTES_THIS_CYCLE=0

        rm -f ${PROGRESS}

        dd bs=$BLKSZ of=${TMPFILE} 2>&1 \
                | grep --line-buffered -E '[[:digit:]]* bytes' \
                | awk '{ print $1 }' >> ${PROGRESS} &

        # Loop while the 'dd' exists. It would be 'more better' if we
        # actually looked for the specific child ID of the running 
        # process by identifying which child process it was. If someone
        # else is running dd, it will mess things up.

        # My PID handling is dumb, it assumes you only have one running dd on
        # the system, this should be fixed to just get the PID of the child
        # process from the shell.

        while [ $(pidof dd) -gt 1 ]; do

                # PROTIP: You can sleep partial seconds (at least on linux)
                sleep .5    

                # Force dd to update us on it's progress (which gets
                # redirected to $PROGRESS file.
                # 
                # dumb pid handling again
                pkill -USR1 dd

                local BYTES_THIS_CYCLE=$(tail -1 $PROGRESS)
                local XFER_BLKS=$(((BYTES_THIS_CYCLE-BYTES_LAST_CYCLE)/BLKSZ))

                # Don't print anything unless we've got 1 block or more.
                # This allows for stdin/stderr interactions to occur
                # without printing a hash erroneously.

                # Also makes it possible for you to background 'scp',
                # but still use the /dev/stdout trick _even_ if scp
                # (inevitably) asks for a password. 
                #
                # Fancy!

                if [ $XFER_BLKS -gt 0 ]; then
                        printf "#%0.s" $(seq 0 $XFER_BLKS)
                        BYTES_LAST_CYCLE=$BYTES_THIS_CYCLE
                fi
        done

        local SIZE=$(stat -c"%s" $TMPFILE)
        local NOW=$(date +"%s")

        if [ $NOW -eq 0 ]; then
                NOW=1
        fi

        local DURATION=$(($NOW-$START))
        local BYTES_PER_SECOND=$(( SIZE / DURATION ))
        local KBPS=$((SIZE/DURATION/1024))
        local MD5=$(md5sum $TMPFILE | awk '{ print $1 }')

        # This function prints out ugly stuff suitable for eval() 
        # rather than a pretty string. This makes it a bit more 
        # flexible if you have a custom format (or dare I say, locale?)

        printf "\nDURATION=%d\nBYTES=%d\nKBPS=%f\nMD5=%s\n" \
            $DURATION \
            $SIZE \
            $KBPS \
            $MD5
}

Exemplos:

echo "wget"
wget -q -O /dev/stdout http://www.blah.com/somefile.zip | progress_filter

echo "tftp"
tftp -l /dev/stdout -g -r something/firmware.bin 192.168.1.1 | progress_filter

echo "scp"
scp user@192.168.1.1:~/myfile.tar /dev/stdout | progress_filter

Idéia decente, desde que você tenha o tamanho do arquivo antecipadamente, poderá fornecer um valor agregado maior do que o pv dessa maneira, mas sinalizar cegamente que pidof ddé assustador.

Tentativa de chamar isso com '# My manipulação PID é idiota'
synthesizerpatel

Você pode, talvez, capturar $!a partir dde esperar [[ -e /proc/${DD_PID} ]].

0

Caso você precise mostrar uma barra de progresso temporal (sabendo antecipadamente o horário da exibição), você pode usar o Python da seguinte maneira:

#!/bin/python
from time import sleep
import sys

if len(sys.argv) != 3:
    print "Usage:", sys.argv[0], "<total_time>", "<progressbar_size>"
    exit()

TOTTIME=float(sys.argv[1])
BARSIZE=float(sys.argv[2])

PERCRATE=100.0/TOTTIME
BARRATE=BARSIZE/TOTTIME

for i in range(int(TOTTIME)+1):
    sys.stdout.write('\r')
    s = "[%-"+str(int(BARSIZE))+"s] %d%% "
    sys.stdout.write(s % ('='*int(BARRATE*i), int(PERCRATE*i)))
    sys.stdout.flush()
    SLEEPTIME = 1.0
    if i == int(TOTTIME): SLEEPTIME = 0.1
    sleep(SLEEPTIME)
print ""

Supondo que você tenha salvo o script Python como progressbar.py, é possível mostrar a barra de progresso do script bash executando o seguinte comando:

python progressbar.py 10 50

Ele mostraria uma barra de progresso do tamanho de 50caracteres e "em execução" por 10segundos.


0

Eu desenvolvi a resposta fornecida pelo medo

Isso se conecta a um banco de dados Oracle para recuperar o progresso de uma restauração do RMAN.

#!/bin/bash

 # 1. Create ProgressBar function
 # 1.1 Input is currentState($1) and totalState($2)
 function ProgressBar {
 # Process data
let _progress=(${1}*100/${2}*100)/100
let _done=(${_progress}*4)/10
let _left=40-$_done
# Build progressbar string lengths
_fill=$(printf "%${_done}s")
_empty=$(printf "%${_left}s")

# 1.2 Build progressbar strings and print the ProgressBar line
# 1.2.1 Output example:
# 1.2.1.1 Progress : [########################################] 100%
printf "\rProgress : [${_fill// /#}${_empty// /-}] ${_progress}%%"

}

function rman_check {
sqlplus -s / as sysdba <<EOF
set heading off
set feedback off
select
round((sofar/totalwork) * 100,0) pct_done
from
v\$session_longops
where
totalwork > sofar
AND
opname NOT LIKE '%aggregate%'
AND
opname like 'RMAN%';
exit
EOF
}

# Variables
_start=1

# This accounts as the "totalState" variable for the ProgressBar function
_end=100

_rman_progress=$(rman_check)
#echo ${_rman_progress}

# Proof of concept
#for number in $(seq ${_start} ${_end})

while [ ${_rman_progress} -lt 100 ]
do

for number in _rman_progress
do
sleep 10
ProgressBar ${number} ${_end}
done

_rman_progress=$(rman_check)

done
printf '\nFinished!\n'

0
#!/bin/bash

function progress_bar() {
    bar=""
    total=10
    [[ -z $1 ]] && input=0 || input=${1}
    x="##"
   for i in `seq 1 10`; do
        if [ $i -le $input ] ;then
            bar=$bar$x
        else
            bar="$bar  "
       fi
    done
    #pct=$((200*$input/$total % 2 + 100*$input/$total))
    pct=$(($input*10))
    echo -ne "Progress : [ ${bar} ] (${pct}%) \r"    
    sleep 1
    if [ $input -eq 10 ] ;then
        echo -ne '\n'
    fi

}

poderia criar uma função que desenha isso em uma escala, digamos 1 a 10 para o número de barras:

progress_bar 1
echo "doing something ..."
progress_bar 2
echo "doing something ..."
progress_bar 3
echo "doing something ..."
progress_bar 8
echo "doing something ..."
progress_bar 10

0
#!/bin/bash
tot=$(wc -c /proc/$$/fd/255 | awk '/ /{print $1}')
now() {
echo $(( 100* ($(awk '/^pos:/{print $2}' < /proc/$$/fdinfo/255)-166) / (tot-166) )) "%"
}
now;
now;
now;
now;
now;
now;
now;
now;
now;

resultado:

0 %
12 %
25 %
37 %
50 %
62 %
75 %
87 %
100 %

nota: se em vez de 255 você colocar 1, monitorará o padrão em ... com 2 o padrão (mas você deve modificar a fonte para definir "tot" no tamanho do arquivo de saída projetado)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.