Non ho visto nulla di simile e tutte le funzioni personalizzate qui sembrano concentrarsi solo sul rendering, quindi ... la mia semplicissima soluzione conforme a POSIX di seguito con spiegazioni dettagliate perché questa domanda non è banale.
TL; DR
Il rendering della barra di avanzamento è molto semplice. Stimare quanto dovrebbe essere reso è una questione diversa. Ecco come visualizzare (animare) la barra di avanzamento: puoi copiare e incollare questo esempio in un file ed eseguirlo:
#!/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}
- valori da 1 a 20
echo -n
- stampa senza nuova riga alla fine
echo -e
- interpretare caratteri speciali durante la stampa
"\r"
- ritorno a capo, un carattere speciale per tornare all'inizio della riga
Puoi farlo renderizzare qualsiasi contenuto a qualsiasi velocità, quindi questo metodo è molto universale, ad esempio spesso usato per la visualizzazione di "hacking" nei film sciocchi, senza scherzare.
Risposta completa
Il problema principale è come determinare il $i
valore, ovvero quanto della barra di avanzamento visualizzare. Nell'esempio sopra ho appena lasciato che aumenti in for
loop per illustrare il principio, ma un'applicazione di vita reale userebbe un loop infinito e calcolerebbe la $i
variabile su ogni iterazione. Per effettuare tale calcolo sono necessari i seguenti ingredienti:
- quanto lavoro c'è da fare
- quanto lavoro è stato fatto finora
Nel caso cp
sia necessario la dimensione di un file sorgente e la dimensione del file di destinazione:
#!/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
- controlla le statistiche del file
-c
- restituisce un valore formattato
%s
- dimensione totale
In caso di operazioni come la decompressione dei file, il calcolo della dimensione di origine è leggermente più difficile ma comunque facile come ottenere la dimensione di un file non compresso:
#!/bin/sh
src_size=$(gzip -l "$src" | tail -n1 | tr -s ' ' | cut -d' ' -f3)
gzip -l
- visualizza informazioni sull'archivio zip
tail -n1
- lavorare con 1 riga dal basso
tr -s ' '
- traduci più spazi in uno (schiacciali)
cut -d' ' -f3
- tagliare la terza colonna delimitata da spazi
Ecco la carne del problema, però. Questa soluzione è sempre meno generale. Tutti i calcoli dell'avanzamento effettivo sono strettamente legati al dominio che si sta tentando di visualizzare, si tratta di un'operazione a singolo file, un conto alla rovescia del timer, un numero crescente di file in una directory, un'operazione su più file, ecc. Pertanto, non può essere riutilizzato. L'unica parte riutilizzabile è il rendering della barra di avanzamento. Per riutilizzarlo devi estrarlo e salvarlo in un file (ad es. /usr/lib/progress_bar.sh
), Quindi definire le funzioni che calcolano i valori di input specifici per il tuo dominio. Ecco come potrebbe apparire un codice generalizzato (ho anche reso $BAR
dinamico perché le persone lo chiedevano, il resto dovrebbe essere chiaro ormai):
#!/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
- un built-in per la stampa di materiale in un determinato formato
printf '%50s'
- non stampare nulla, riempirlo con 50 spazi
tr ' ' '#'
- tradurre ogni spazio in segno di hash
Ed è così che lo useresti:
#!/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
Ovviamente può essere racchiuso in una funzione, riscritto per funzionare con flussi convogliati, riscritto in un'altra lingua, qualunque sia il tuo veleno.