Come aggiungere una barra di avanzamento a uno script di shell?
Domanda
Quando si esegue lo scripting in bash o qualsiasi altra shell in * NIX, mentre si esegue un comando che richiederà più di qualche secondo, è necessaria una barra di avanzamento.
Ad esempio, copiando un file di grandi dimensioni, aprendo un file tar di grandi dimensioni.
Quali modi mi consiglia di aggiungere barre di avanzamento agli script di shell?
Soluzione
Puoi implementarlo sovrascrivendo una riga. Usa \ r
per tornare all'inizio della riga senza scrivere \ n
sul terminale.
Scrivi \ n
quando hai finito per far avanzare la linea.
Usa echo -ne
per:
- non stampa
\ n
e - per riconoscere sequenze di escape come
\ r
.
Ecco una demo:
echo -ne '##### (33%)\r'
sleep 1
echo -ne '############# (66%)\r'
sleep 1
echo -ne '####################### (100%)\r'
echo -ne '\n'
In un commento qui sotto, puk menziona questo "errore". se inizi con una linea lunga e poi vuoi scrivere una linea corta: in questo caso, dovrai sovrascrivere la lunghezza della linea lunga (ad es. con spazi).
Altri suggerimenti
Potresti anche essere interessato a come fare un filatore :
Posso fare una spinner in Bash?
Certo!
i=1 sp="/-\|" echo -n ' ' while true do printf "\b${sp:i++%${#sp}:1}" done
Ogni volta che il ciclo viene ripetuto, viene visualizzato il carattere successivo nello sp stringa, avvolgendosi mentre raggiunge la fine. (i è la posizione di il carattere corrente da visualizzare e $ {# sp} è la lunghezza dello sp stringa).
La stringa \ b è sostituita da un carattere "backspace". In alternativa, potresti giocare con \ r per tornare all'inizio della riga.
Se vuoi che rallenti, inserisci un comando sleep all'interno del loop (dopo la stampa).
Un equivalente POSIX sarebbe:
sp='/-\|' printf ' ' while true; do printf '\b%.1s' "$sp" sp=${sp#?}${sp%???} done
Se hai già un loop che fa molto lavoro, puoi chiamare il seguente funzione all'inizio di ogni iterazione per aggiornare il file filatore:
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
Alcuni post hanno mostrato come visualizzare l'avanzamento del comando. Per calcolarlo, devi vedere quanto hai progredito. Sui sistemi BSD alcuni comandi, come dd (1), accettano un segnale SIGINFO
e segnaleranno i loro progressi. Sui sistemi Linux alcuni comandi rispondono in modo simile a SIGUSR1
. Se questa funzione è disponibile, è possibile reindirizzare l'input tramite dd
per monitorare il numero di byte elaborati.
In alternativa, puoi utilizzare lsof
per ottenere l'offset di il puntatore di lettura del file e quindi calcolare l'avanzamento. Ho scritto un comando, chiamato pmonitor , che mostra l'avanzamento dell'elaborazione di un processo o file specificato. Con esso puoi fare cose, come le seguenti.
$ pmonitor -c gzip
/home/dds/data/mysql-2015-04-01.sql.gz 58.06%
Una versione precedente degli script della shell Linux e FreeBSD appare sul il mio blog .
usa il comando linux pv:
non conosce le dimensioni se si trova nel mezzo dello stream, ma fornisce una velocità e un totale e da lì puoi capire quanto tempo dovrebbe impiegare e ottenere feedback in modo da sapere che non si è bloccato.
Ho ottenuto una semplice barra di avanzamento che ho scritto l'altro giorno:
#!/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- Ho ottenuto una semplice barra di avanzamento che ho scritto l'altro giorno:
<*>
O prendilo da,
https://github.com/fearside/ProgressBar/
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'
O prendilo da,
https://github.com/fearside/ProgressBar/
Stavo cercando qualcosa di più sexy della risposta selezionata, così ha fatto la mia sceneggiatura.
Anteprima
sorgente
L'ho messo su github progress-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
GNU tar ha un'opzione utile che offre una funzionalità di una semplice barra di avanzamento.
(...) Un'altra azione di checkpoint disponibile è "punto" (o "."). Indica a tar di stampare un singolo punto sul flusso di elenco standard, ad esempio:
$ tar -c --checkpoint=1000 --checkpoint-action=dot /var
...
Lo stesso effetto può essere ottenuto da:
$ tar -c --checkpoint=.1000 /var
Un metodo più semplice che funziona sul mio sistema usando l'utilità 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
Vorrei anche contribuire con i miei progressi personali bar
Raggiunge la precisione dei sotto-caratteri usando Mezzi blocchi unicode
Il codice è incluso
Questo ti permette di visualizzare che un comando sta ancora eseguendo:
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
Questo creerà un infinito ciclo while che viene eseguito in background e fa eco a ". " ogni secondo. Questo mostrerà .
nella shell. Esegui il comando tar
o qualsiasi altro comando tu voglia. Al termine dell'esecuzione di quel comando, uccidi l'ultimo processo in esecuzione in background, ovvero il infinito ciclo while .
Non ho visto niente di simile, quindi ... la mia soluzione molto semplice:
#!/bin/bash
BAR='####################' # this is full bar, mine is 20 chars
for i in {1..20}; do
echo -ne "\r${BAR:0:$i}" # print $i chars of $BAR from 0 position
sleep .1
done
-
echo -n
- stampa senza una nuova riga alla fine -
echo -e
- interpreta i caratteri speciali durante la stampa -
" \ r "
- ritorno a capo, un carattere speciale per tornare all'inizio della riga
L'ho usato molto tempo fa in un semplice video di "hacking" per simulare il codice di battitura. ;)
La maggior parte dei comandi unix non ti darà il tipo di feedback diretto da cui puoi farlo. Alcuni ti daranno output su stdout o stderr che puoi usare.
Per qualcosa come tar puoi usare l'opzione -v e reindirizzare l'output a un programma che aggiorna una piccola animazione per ogni riga che legge. Mentre tar scrive un elenco di file che è stato svelato, il programma può aggiornare l'animazione. Per completare una percentuale dovresti conoscere il numero di file e contare le righe.
cp non fornisce questo tipo di output per quanto ne so. Per monitorare l'avanzamento di cp dovresti monitorare i file di origine e di destinazione e controllare le dimensioni della destinazione. Puoi scrivere un piccolo programma c usando la stat (2) per ottenere il dimensione del file. Questo leggerebbe la dimensione della fonte, quindi eseguirà il polling del file di destinazione e aggiornerebbe una barra% completa in base alla dimensione del file scritto fino ad oggi.
La mia soluzione visualizza la percentuale del tarball che
è attualmente non compresso e scritto. Io lo uso
quando si scrivono immagini di filesystem di root da 2 GB. Tu veramente
serve una barra di avanzamento per queste cose. Quello che faccio è usare
gzip --list
per ottenere la dimensione totale non compressa di
tarball. Da questo calcolo il fattore di blocco necessario
per dividere il file in 100 parti. Infine, stampo a
messaggio di checkpoint per ogni blocco. Per un file da 2 GB questo
dà circa 10 MB un blocco. Se è troppo grande, puoi farlo
dividi BLOCKING_FACTOR per 10 o 100, ma poi lo è
più difficile da stampare piuttosto in termini di percentuale.
Supponendo che tu stia usando Bash, puoi usare il seguente funzione 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}
}
Prima di tutto la barra non è l'unico indicatore di avanzamento di un tubo. L'altro (forse anche più noto) è pv (pipe viewer).
In secondo luogo bar e pv possono essere usati ad esempio in questo modo:
$ bar file1 | wc -l
$ pv file1 | wc -l
o anche:
$ tail -n 100 file1 | bar | wc -l
$ tail -n 100 file1 | pv | wc -l
un trucco utile se vuoi usare bar e pv nei comandi che funzionano con i file forniti in argomenti, come ad es. copia file1 file2, deve utilizzare sostituzione del processo :
$ copy <(bar file1) file2
$ copy <(pv file1) file2
La sostituzione del processo è una cosa magica bash che crea file temporanei di pipe a cinque file / dev / fd / e collega stdout dal processo eseguito (tra parentesi) attraverso questa pipe e la copia lo vede proprio come un normale file (con un'eccezione, può leggilo solo in avanti).
Aggiornamento:
Il comando barstesso consente anche la copia. After man bar:
bar --in-file /dev/rmt/1cbn --out-file \
tape-restore.tar --size 2.4g --buffer-size 64k
Ma a mio avviso la sostituzione del processo è un modo più generico per farlo. Un programma utilizza cp stesso.
Preferisco usare dialog con il parametro --gauge . Viene usato molto spesso nelle installazioni di pacchetti .deb e in altre cose di configurazione di base di molte distro. Quindi non è necessario reinventare la ruota ... di nuovo
Basta inserire un valore int compreso tra 1 e 100 @stdin. Un esempio di base e sciocco:
for a in {1..100}; do sleep .1s; echo $a| dialog --gauge "waiting" 7 30; done
Ho questo file / bin / Wait (con chmod u + x permanenti) per scopi di cottura: 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"
Quindi posso mettere:
Attendi "34 min" " riscalda il forno "
o
Attendi "dic 31" " felice anno nuovo "
Ecco come potrebbe apparire
Caricamento di un file
[##################################################] 100% (137921 / 137921 bytes)
In attesa del completamento di un lavoro
[######################### ] 50% (15 / 30 seconds)
Semplice funzione che la implementa
Puoi semplicemente copiarlo e incollarlo nello script. Non richiede nient'altro per funzionare.
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 (( Ecco come potrebbe apparire
Caricamento di un file
[##################################################] 100% (137921 / 137921 bytes)
In attesa del completamento di un lavoro
[######################### ] 50% (15 / 30 seconds)
Semplice funzione che la implementa
Puoi semplicemente copiarlo e incollarlo nello script. Non richiede nient'altro per funzionare.
# 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"
Esempio di utilizzo
Qui, cariciamo un file e ridisegniamo la barra di avanzamento ad ogni iterazione. Non importa quale lavoro viene effettivamente eseguito, purché possiamo ottenere 2 valori: valore massimo e valore corrente.
Nell'esempio seguente il valore massimo è dimensione_file
e il valore corrente viene fornito da alcune funzioni e si chiama upload_bytes
.
<*>_max < 1 )); then __max=1; fi # anti zero division protection
local __percentage=$(( 100 - ( Ecco come potrebbe apparire
Caricamento di un file
[##################################################] 100% (137921 / 137921 bytes)
In attesa del completamento di un lavoro
[######################### ] 50% (15 / 30 seconds)
Semplice funzione che la implementa
Puoi semplicemente copiarlo e incollarlo nello script. Non richiede nient'altro per funzionare.
<*>
Esempio di utilizzo
Qui, cariciamo un file e ridisegniamo la barra di avanzamento ad ogni iterazione. Non importa quale lavoro viene effettivamente eseguito, purché possiamo ottenere 2 valori: valore massimo e valore corrente.
Nell'esempio seguente il valore massimo è dimensione_file
e il valore corrente viene fornito da alcune funzioni e si chiama upload_bytes
.
<*>_max*100 - Ecco come potrebbe apparire
Caricamento di un file
[##################################################] 100% (137921 / 137921 bytes)
In attesa del completamento di un lavoro
[######################### ] 50% (15 / 30 seconds)
Semplice funzione che la implementa
Puoi semplicemente copiarlo e incollarlo nello script. Non richiede nient'altro per funzionare.
<*>
Esempio di utilizzo
Qui, cariciamo un file e ridisegniamo la barra di avanzamento ad ogni iterazione. Non importa quale lavoro viene effettivamente eseguito, purché possiamo ottenere 2 valori: valore massimo e valore corrente.
Nell'esempio seguente il valore massimo è dimensione_file
e il valore corrente viene fornito da alcune funzioni e si chiama upload_bytes
.
<*>_value*100) / Ecco come potrebbe apparire
Caricamento di un file
[##################################################] 100% (137921 / 137921 bytes)
In attesa del completamento di un lavoro
[######################### ] 50% (15 / 30 seconds)
Semplice funzione che la implementa
Puoi semplicemente copiarlo e incollarlo nello script. Non richiede nient'altro per funzionare.
<*>
Esempio di utilizzo
Qui, cariciamo un file e ridisegniamo la barra di avanzamento ad ogni iterazione. Non importa quale lavoro viene effettivamente eseguito, purché possiamo ottenere 2 valori: valore massimo e valore corrente.
Nell'esempio seguente il valore massimo è dimensione_file
e il valore corrente viene fornito da alcune funzioni e si chiama upload_bytes
.
<*>_max ))
# Rescale the bar according to the progress bar width
local __num_bar=$(( Ecco come potrebbe apparire
Caricamento di un file
[##################################################] 100% (137921 / 137921 bytes)
In attesa del completamento di un lavoro
[######################### ] 50% (15 / 30 seconds)
Semplice funzione che la implementa
Puoi semplicemente copiarlo e incollarlo nello script. Non richiede nient'altro per funzionare.
<*>
Esempio di utilizzo
Qui, cariciamo un file e ridisegniamo la barra di avanzamento ad ogni iterazione. Non importa quale lavoro viene effettivamente eseguito, purché possiamo ottenere 2 valori: valore massimo e valore corrente.
Nell'esempio seguente il valore massimo è dimensione_file
e il valore corrente viene fornito da alcune funzioni e si chiama upload_bytes
.
<*>_percentage * $PROGRESS_BAR_WIDTH / 100 ))
# Draw progress bar
printf "["
for b in $(seq 1 Ecco come potrebbe apparire
Caricamento di un file
[##################################################] 100% (137921 / 137921 bytes)
In attesa del completamento di un lavoro
[######################### ] 50% (15 / 30 seconds)
Semplice funzione che la implementa
Puoi semplicemente copiarlo e incollarlo nello script. Non richiede nient'altro per funzionare.
<*>
Esempio di utilizzo
Qui, cariciamo un file e ridisegniamo la barra di avanzamento ad ogni iterazione. Non importa quale lavoro viene effettivamente eseguito, purché possiamo ottenere 2 valori: valore massimo e valore corrente.
Nell'esempio seguente il valore massimo è dimensione_file
e il valore corrente viene fornito da alcune funzioni e si chiama upload_bytes
.
<*>_num_bar); do printf "#"; done
for s in $(seq 1 $(( $PROGRESS_BAR_WIDTH - Ecco come potrebbe apparire
Caricamento di un file
[##################################################] 100% (137921 / 137921 bytes)
In attesa del completamento di un lavoro
[######################### ] 50% (15 / 30 seconds)
Semplice funzione che la implementa
Puoi semplicemente copiarlo e incollarlo nello script. Non richiede nient'altro per funzionare.
<*>
Esempio di utilizzo
Qui, cariciamo un file e ridisegniamo la barra di avanzamento ad ogni iterazione. Non importa quale lavoro viene effettivamente eseguito, purché possiamo ottenere 2 valori: valore massimo e valore corrente.
Nell'esempio seguente il valore massimo è dimensione_file
e il valore corrente viene fornito da alcune funzioni e si chiama upload_bytes
.
<*>_num_bar ))); do printf " "; done
printf "] Ecco come potrebbe apparire
Caricamento di un file
[##################################################] 100% (137921 / 137921 bytes)
In attesa del completamento di un lavoro
[######################### ] 50% (15 / 30 seconds)
Semplice funzione che la implementa
Puoi semplicemente copiarlo e incollarlo nello script. Non richiede nient'altro per funzionare.
<*>
Esempio di utilizzo
Qui, cariciamo un file e ridisegniamo la barra di avanzamento ad ogni iterazione. Non importa quale lavoro viene effettivamente eseguito, purché possiamo ottenere 2 valori: valore massimo e valore corrente.
Nell'esempio seguente il valore massimo è dimensione_file
e il valore corrente viene fornito da alcune funzioni e si chiama upload_bytes
.
<*>_percentage%% ( Ecco come potrebbe apparire
Caricamento di un file
[##################################################] 100% (137921 / 137921 bytes)
In attesa del completamento di un lavoro
[######################### ] 50% (15 / 30 seconds)
Semplice funzione che la implementa
Puoi semplicemente copiarlo e incollarlo nello script. Non richiede nient'altro per funzionare.
<*>
Esempio di utilizzo
Qui, cariciamo un file e ridisegniamo la barra di avanzamento ad ogni iterazione. Non importa quale lavoro viene effettivamente eseguito, purché possiamo ottenere 2 valori: valore massimo e valore corrente.
Nell'esempio seguente il valore massimo è dimensione_file
e il valore corrente viene fornito da alcune funzioni e si chiama upload_bytes
.
<*>_value / Ecco come potrebbe apparire
Caricamento di un file
[##################################################] 100% (137921 / 137921 bytes)
In attesa del completamento di un lavoro
[######################### ] 50% (15 / 30 seconds)
Semplice funzione che la implementa
Puoi semplicemente copiarlo e incollarlo nello script. Non richiede nient'altro per funzionare.
<*>
Esempio di utilizzo
Qui, cariciamo un file e ridisegniamo la barra di avanzamento ad ogni iterazione. Non importa quale lavoro viene effettivamente eseguito, purché possiamo ottenere 2 valori: valore massimo e valore corrente.
Nell'esempio seguente il valore massimo è dimensione_file
e il valore corrente viene fornito da alcune funzioni e si chiama upload_bytes
.
<*>_max Ecco come potrebbe apparire
Caricamento di un file
[##################################################] 100% (137921 / 137921 bytes)
In attesa del completamento di un lavoro
[######################### ] 50% (15 / 30 seconds)
Semplice funzione che la implementa
Puoi semplicemente copiarlo e incollarlo nello script. Non richiede nient'altro per funzionare.
<*>
Esempio di utilizzo
Qui, cariciamo un file e ridisegniamo la barra di avanzamento ad ogni iterazione. Non importa quale lavoro viene effettivamente eseguito, purché possiamo ottenere 2 valori: valore massimo e valore corrente.
Nell'esempio seguente il valore massimo è dimensione_file
e il valore corrente viene fornito da alcune funzioni e si chiama upload_bytes
.
<*>_unit)\r"
}
Esempio di utilizzo
Qui, cariciamo un file e ridisegniamo la barra di avanzamento ad ogni iterazione. Non importa quale lavoro viene effettivamente eseguito, purché possiamo ottenere 2 valori: valore massimo e valore corrente.
Nell'esempio seguente il valore massimo è dimensione_file
e il valore corrente viene fornito da alcune funzioni e si chiama upload_bytes
.
Barra di avanzamento stile APT (non interrompe l'output normale)
EDIT: per una versione aggiornata controlla la mia pagina github
Non ero soddisfatto delle risposte a questa domanda. Quello che stavo cercando personalmente era una barra di avanzamento elegante come viene visto da APT.
Ho dato un'occhiata al codice sorgente C per APT e ho deciso di scrivere il mio equivalente per bash.
Questa barra di avanzamento rimarrà bene nella parte inferiore del terminale e non interferirà con alcun output inviato al terminale.
Si noti che la barra è attualmente fissata a 100 caratteri di larghezza. Se vuoi ridimensionarlo alle dimensioni del terminale, anche questo è abbastanza facile da realizzare (La versione aggiornata sulla mia pagina github lo gestisce bene).
Pubblicherò la mia sceneggiatura qui. Esempio di utilizzo:
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
Lo script (consiglio vivamente la versione sul mio 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}"
}
per me il più facile da usare e il più bello finora è il comando pv
o bar
come alcuni ragazzi hanno già scritto
ad esempio: è necessario eseguire un backup dell'intera unità con dd
normalmente usi dd if = " $ input_drive_path " di = " $ output_file_path "
con pv
puoi farlo in questo modo:
dd if = " $ input_drive_path " | pv | dd di = " $ output_file_path "
e il progresso passa direttamente a STDOUT
come questo:
7.46GB 0:33:40 [3.78MB/s] [ <=> ]
dopo aver fatto il riepilogo compare
15654912+0 records in
15654912+0 records out
8015314944 bytes (8.0 GB) copied, 2020.49 s, 4.0 MB/s
Molte risposte descrivono come scrivere i propri comandi per stampare '\ r' + $ some_sort_of_progress_msg
. Il problema a volte è che stampare centinaia di questi aggiornamenti al secondo rallenterà il processo.
Tuttavia, se uno qualsiasi dei tuoi processi produce output (es. 7z a -r newZipFile myFolder
produrrà ogni nome di file man mano che lo comprime) allora esiste una soluzione più semplice, veloce, indolore e personalizzabile.
Installa il modulo 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
Aiuto: tqdm -h
. Un esempio che utilizza più opzioni:
$ find / -name '*.py' -exec cat \{} \; | tqdm --unit loc --unit_scale True | wc -l
Come bonus puoi anche usare tqdm
per racchiudere gli iterabili nel codice python.
https://github.com/tqdm/tqdm/blob/ master / README.rst # modulo
Questo è applicabile solo usando zenity di gnome. Zenity offre un'ottima interfaccia nativa per bash degli script: https://help.gnome.org/users/zenity/stable/
Dall'esempio della barra di avanzamento 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
Ho usato una risposta da Creazione di una stringa di caratteri ripetuti nello script shell per la ripetizione del carattere. Ho due versioni bash relativamente piccole per gli script che devono visualizzare la barra di avanzamento (ad esempio, un ciclo che attraversa molti file, ma non è utile per file tar di grandi dimensioni o operazioni di copia). La più veloce è composta da due funzioni, una per preparare le stringhe per la visualizzazione della barra:
preparebar() {
# $1 - bar length
# $2 - bar char
barlen=$1
barspaces=$(printf "%*s" "$1")
barchars=$(printf "%*s" "$1" | tr ' ' "$2")
}
e uno per visualizzare una barra di avanzamento:
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
}
Potrebbe essere usato come:
preparebar 50 "#"
che significa preparare stringhe per barra con 50 " # " caratteri e successivamente:
progressbar 35 80
visualizzerà il numero di " # " caratteri che corrispondono al rapporto 35/80:
[##################### ]
Tieni presente che la funzione visualizza la barra sulla stessa riga più e più volte finché tu (o qualche altro programma) non stampi una nuova riga. Se si inserisce -1 come primo parametro, la barra verrebbe cancellata:
progressbar -1 80
La versione più lenta è tutta in una funzione:
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 può essere usato come (lo stesso esempio sopra):
progressbar 35 80 50
Se hai bisogno della barra di avanzamento su stderr, aggiungi > & amp; 2
alla fine di ogni comando printf.
Per indicare l'avanzamento dell'attività, provare i seguenti comandi:
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;
o
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;
o
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;
o
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;
È possibile utilizzare flag / variabili all'interno del ciclo while per verificare e visualizzare il valore / l'entità dei progressi.
Basato sul lavoro di Edouard Lopez, ho creato una barra di avanzamento che si adatta alle dimensioni dello schermo, qualunque esso sia. Dai un'occhiata.
È anche pubblicato su 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: <*> [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
Godetevi
Utilizzando i suggerimenti sopra elencati, ho deciso di implementare la mia barra di avanzamento.
#!/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 "$@"
Ho realizzato una versione shell pura per un sistema incorporato sfruttando:
-
/ usr / bin / dd's SIGUSR1 funzione di gestione del segnale.
Fondamentalmente, se invii un 'kill SIGUSR1 $ (pid_of_running_dd_process)', verrà emesso un riepilogo della velocità effettiva e dell'importo trasferito.
-
backgrounding dd e quindi interrogandolo regolarmente per gli aggiornamenti e generando hash tick come facevano i client ftp della vecchia scuola.
-
Utilizzo di / dev / stdout come destinazione per programmi non stdout compatibili come scp
Il risultato finale ti consente di eseguire qualsiasi operazione di trasferimento di file e ottenere aggiornamenti sui progressi che sembrano output 'hash' FTP della vecchia scuola in cui avresti semplicemente un segno di hash per ogni X byte.
Questo è quasi un codice di qualità di produzione, ma hai capito. Penso che sia carino.
Per quello che vale, l'effettivo conteggio dei byte potrebbe non riflettersi correttamente nel numero di hash - potresti averne uno in più o in meno a seconda dei problemi di arrotondamento. Non usarlo come parte di uno script di test, è solo un piacere per gli occhi. E sì, sono consapevole che questo è terribilmente inefficiente: è uno script di shell e non mi scuso.
Esempi con wget, scp e tftp forniti alla fine. Dovrebbe funzionare con tutto ciò che ha emesso dati. Assicurati di usare / dev / stdout per programmi che non sono stdout-friendly.
#!/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
}
Esempi:
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
Nel caso in cui sia necessario mostrare una barra di avanzamento temporale (conoscendo in anticipo il tempo di visualizzazione), è possibile utilizzare Python come segue:
#!/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 ""
Quindi, supponendo che tu abbia salvato lo script Python come progressbar.py
, è possibile mostrare la barra di avanzamento dal tuo script bash eseguendo il comando seguente:
python progressbar.py 10 50
Mostrerebbe una barra di avanzamento con caratteri 50
e " in esecuzione " per 10
secondi.
Ho sviluppato la risposta fornita da fearside
Questo si collega a un database Oracle per recuperare l'avanzamento di un ripristino 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- Ho sviluppato la risposta fornita da fearside
Questo si collega a un database Oracle per recuperare l'avanzamento di un ripristino RMAN.
<*>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'
#!/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
}
potrebbe creare una funzione che la disegna su una scala diciamo 1-10 per il numero di barre:
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
#!/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;
uscita:
0 %
12 %
25 %
37 %
50 %
62 %
75 %
87 %
100 %
nota: se invece di 255 inserisci 1 monitorerai lo standard in ... con 2 lo standard out (ma devi modificare la sorgente per impostare " tot " sulla dimensione del file di output proiettato)