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 $i
valor, ou seja, quanto da barra de progresso deve ser exibida. No exemplo acima, deixei que ele aumentasse em for
loop para ilustrar o princípio, mas um aplicativo da vida real usaria um loop infinito e calcularia a $i
variável em cada iteração. Para fazer esse cálculo, ele precisa dos seguintes ingredientes:
- quanto trabalho há para ser feito
- quanto trabalho foi feito até agora
Caso cp
precise 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.