Usar BASH para mostrar un indicador de progreso(trabajo)
shell unix (11)
@ Los comentarios de DavidD sobre la respuesta de Pez Cuckows, este es un ejemplo de cómo se puede capturar el resultado de la barra de progreso en un script y aún ver el control giratorio en la pantalla:
#!/usr/bin/env bash
#############################################################################
###########################################################################
###
### Modified/Rewritten by A.M.Danischewski (c) 2015 v1.1
### Issues: If you find any issues emai1 me at my <first name> dot
### <my last name> at gmail dot com.
###
### Based on scripts posted by Pez Cuckow, William Pursell at:
### http://stackoverflow.com/questions/12498304/using-bash-to-display-/
### a-progress-working-indicator
###
### This program runs a program passed in and outputs a timing of the
### command and it exec''s a new fd for stdout so you can assign a
### variable the output of what was being run.
###
### This is a very new rough draft but could be expanded.
###
### This program is free software: you can redistribute it and/or modify
### it under the terms of the GNU General Public License as published by
### the Free Software Foundation, either version 3 of the License, or
### (at your option) any later version.
###
### This program is distributed in the hope that it will be useful,
### but WITHOUT ANY WARRANTY; without even the implied warranty of
### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
### GNU General Public License for more details.
###
### You should have received a copy of the GNU General Public License
### along with this program. If not, see <http://www.gnu.org/licenses/>.
###########################################################################
#############################################################################
declare CMD="${1}"
shift ## Clip the first value of the $@, the rest are the options.
declare CMD_OPTIONS="$@"
declare CMD_OUTPUT=""
declare TMP_OUTPUT="/tmp/_${0##*/}_$$_$(date +%Y%m%d%H%M%S%N)"
declare -r SPIN_DELAY="0.1"
declare -i PID=
function usage() {
cat <<EOF
Description: ${0##*/}
This program runs a program passed in and outputs a timing of the
command and it exec''s a new fd for stdout so you can assign a variable
the output of what was being run.
Usage: ${0##*/} <command> [command options]
E.g.
>$ ${0##*/} sleep 5 /&/& echo "hello" /| figlet
Running: sleep 5 && echo hello | figlet, PID 2587:/
real 0m5.003s
user 0m0.000s
sys 0m0.002s
_ _ _
| |__ ___| | | ___
| ''_ / / _ / | |/ _ /
| | | | __/ | | (_) |
|_| |_|/___|_|_|/___/
Done..
>$ var=/$(${0##*/} sleep 5 /&/& echo hi)
Running: sleep 5 && echo hi, PID 32229:-
real 0m5.003s
user 0m0.000s
sys 0m0.001s
Done..
>$ echo /$var
hi
EOF
}
function spin_wait() {
local -a spin
spin[0]="-"
spin[1]="//"
spin[2]="|"
spin[3]="/"
echo -en "Running: ${CMD} ${CMD_OPTIONS}, PID ${PID}: " >&3
while kill -0 ${PID} 2>/dev/random; do
for i in "${spin[@]}"; do
echo -ne "/b$i" >&3
sleep ${SPIN_DELAY}
done
done
}
function run_cmd() {
exec 3>$(tty)
eval "time ${CMD} ${CMD_OPTIONS}" 2>>"${TMP_OUTPUT}" | tee "${TMP_OUTPUT}" &
PID=$! # Set global PID to process id of the command we just ran.
spin_wait
echo -en "/n$(< "${TMP_OUTPUT}")/n" >&3
echo -en "Done../n" >&3
rm "${TMP_OUTPUT}"
exec 3>&-
}
if [[ -z "${CMD}" || "${CMD}" =~ ^-. ]]; then
usage | more && exit 0
else
run_cmd
fi
exit 0
Usando un script bash only, ¿cómo puedes proporcionar un indicador de progreso bash?
Entonces puedo ejecutar un comando de forma bash, y mientras se está ejecutando ese comando, que el usuario sepa que algo está sucediendo.
A partir de here se hace referencia a una buena función de centrifugado (con una ligera modificación), que también ayudará al cursor a permanecer en la posición original.
spinner()
{
local pid=$!
local delay=0.75
local spinstr=''|/-/'
while [ "$(ps a | awk ''{print $1}'' | grep $pid)" ]; do
local temp=${spinstr#?}
printf " [%c] " "$spinstr"
local spinstr=$temp${spinstr%"$temp"}
sleep $delay
printf "/b/b/b/b/b/b"
done
printf " /b/b/b/b"
}
con uso:
(a_long_running_task) &
spinner
Aparte del spinner clásico, puedes usar esta barra de progreso
Logra precisión de subcaracter utilizando caracteres de medio bloque
Código incluido en el enlace.
Aquí está mi intento. Soy nuevo en los scripts bash, así que parte de este código puede ser terrible :)
Ejemplo de salida:
El código:
progressBarWidth=20
# Function to draw progress bar
progressBar () {
# Calculate number of fill/empty slots in the bar
progress=$(echo "$progressBarWidth/$taskCount*$tasksDone" | bc -l)
fill=$(printf "%.0f/n" $progress)
if [ $fill -gt $progressBarWidth ]; then
fill=$progressBarWidth
fi
empty=$(($fill-$progressBarWidth))
# Percentage Calculation
percent=$(echo "100/$taskCount*$tasksDone" | bc -l)
percent=$(printf "%0.2f/n" $percent)
if [ $(echo "$percent>100" | bc) -gt 0 ]; then
percent="100.00"
fi
# Output to screen
printf "/r["
printf "%${fill}s" '''' | tr '' '' ▉
printf "%${empty}s" '''' | tr '' '' ░
printf "] $percent%% - $text "
}
## Collect task count
taskCount=33
tasksDone=0
while [ $tasksDone -le $taskCount ]; do
# Do your task
(( tasksDone += 1 ))
# Add some friendly output
text=$(echo "somefile-$tasksDone.dat")
# Draw the progress bar
progressBar $taskCount $taskDone $text
sleep 0.01
done
echo
Puede ver la fuente aquí: https://gist.github.com/F1LT3R/fa7f102b08a514f2c535
Aquí hay un ejemplo de un "indicador de actividad" para una prueba de velocidad de conexión a Internet a través del comando linux "speedtest-cli":
printf ''/n/tInternet speed test: ''
# http://.com/questions/12498304/using-bash-to-display-a-progress-working-indicator
spin[0]="-"
spin[1]="//"
spin[2]="|"
spin[3]="/"
# http://.com/questions/20165057/executing-bash-loop-while-command-is-running
speedtest > .st.txt & ## & : continue running script
pid=$! ## PID of last command
# If this script is killed, kill ''speedtest'':
trap "kill $pid 2> /dev/null" EXIT
# While ''speedtest'' is running:
while kill -0 $pid 2> /dev/null; do
for i in "${spin[@]}"
do
echo -ne "/b$i"
sleep 0.1
done
done
# Disable the trap on a normal exit:
trap - EXIT
printf "/n/t "
grep Download: .st.txt
printf "/t "
grep Upload: .st.txt
echo ''''
rm -f st.txt
Actualización - ejemplo:
Aquí un simple onliner, que uso:
while true; do for X in ''-'' ''/'' ''|'' ''/'; do echo -en "/b$X"; sleep 0.1; done; done
Barra de progreso psicodélica para scripts de bash. Llamar por línea de comando como ''./progressbar xy'' donde ''x'' es un tiempo en segundos e ''y'' es un mensaje para mostrar. La función interna progressbar () también funciona de forma independiente y toma ''x'' como un porcentaje e ''y'' como un mensaje.
#!/bin/bash
if [ "$#" -eq 0 ]; then echo "x is /"time in seconds/" and z is /"message/""; echo "Usage: progressbar x z"; exit; fi
progressbar() {
local loca=$1; local loca2=$2;
declare -a bgcolors; declare -a fgcolors;
for i in {40..46} {100..106}; do
bgcolors+=("$i")
done
for i in {30..36} {90..96}; do
fgcolors+=("$i")
done
local u=$(( 50 - loca ));
local y; local t;
local z; z=$(printf ''%*s'' "$u");
local w=$(( loca * 2 ));
local bouncer=".oO°Oo.";
for ((i=0;i<loca;i++)); do
t="${bouncer:((i%${#bouncer})):1}"
bgcolor="//E[${bgcolors[RANDOM % 14]}m //033[m"
y+="$bgcolor";
done
fgcolor="//E[${fgcolors[RANDOM % 14]}m"
echo -ne " $fgcolor$t$y$z$fgcolor$t //E[96m(//E[36m$w%//E[96m)//E[92m $fgcolor$loca2//033[m/r"
};
timeprogress() {
local loca="$1"; local loca2="$2";
loca=$(bc -l <<< scale=2/;"$loca/50")
for i in {1..50}; do
progressbar "$i" "$loca2";
sleep "$loca";
done
echo -e "/n"
};
timeprogress "$1" "$2"
En este ejemplo usando SCP, estoy demostrando cómo agarrar el id del proceso (pid) y luego hacer algo mientras el proceso se está ejecutando.
Esto muestra un icono de spinnng simple.
/usr/bin/scp [email protected]:file somewhere 2>/dev/null &
pid=$! # Process Id of the previous running command
spin[0]="-"
spin[1]="//"
spin[2]="|"
spin[3]="/"
echo -n "[copying] ${spin[0]}"
while [ kill -0 $pid ]
do
for i in "${spin[@]}"
do
echo -ne "/b$i"
sleep 0.1
done
done
La solución de William Pursell
/usr/bin/scp [email protected]:file somewhere 2>/dev/null &
pid=$! # Process Id of the previous running command
spin=''-/|/''
i=0
while kill -0 $pid 2>/dev/null
do
i=$(( (i+1) %4 ))
printf "/r${spin:$i:1}"
sleep .1
done
Extendí la respuesta de de en su respuesta al mostrar un mensaje de información variable después de la flecha giratoria:
#!/usr/bin/env bash
function spinner() {
local info="$1"
local pid=$!
local delay=0.75
local spinstr=''|/-/'
while kill -0 $pid 2> /dev/null; do
local temp=${spinstr#?}
printf " [%c] $info" "$spinstr"
local spinstr=$temp${spinstr%"$temp"}
sleep $delay
local reset="/b/b/b/b/b/b"
for ((i=1; i<=$(echo $info | wc -c); i++)); do
reset+="/b"
done
printf $reset
done
printf " /b/b/b/b"
}
# usage:
(a_long_running_task) &
spinner "performing long running task..."
No me gusta que si la salida stdout con un spinner se redirige a un archivo, less
muestra ^H
para cada retroceso en lugar de evitarlos en una salida de archivo. ¿Es posible con un spinner fácil como este?
Si tiene una forma de estimar el porcentaje realizado, como la cantidad actual de archivos procesados y el número total, puede hacer un medidor de progreso lineal simple con un poco de matemática y suposiciones sobre el ancho de la pantalla.
count=0
total=34
pstr="[=======================================================================]"
while [ $count -lt $total ]; do
sleep 0.5 # this is work
count=$(( $count + 1 ))
pd=$(( $count * 73 / $total ))
printf "/r%3d.%1d%% %.${pd}s" $(( $count * 100 / $total )) $(( ($count * 1000 / $total) % 10 )) $pstr
done
O en lugar de un medidor lineal, puedes calcular el tiempo restante. Es tan preciso como otras cosas similares.
count=0
total=34
start=`date +%s`
while [ $count -lt $total ]; do
sleep 0.5 # this is work
cur=`date +%s`
count=$(( $count + 1 ))
pd=$(( $count * 73 / $total ))
runtime=$(( $cur-$start ))
estremain=$(( ($runtime * $total / $count)-$runtime ))
printf "/r%d.%d%% complete ($count of $total) - est %d:%0.2d remaining/e[K" $(( $count*100/$total )) $(( ($count*1000/$total)%10)) $(( $estremain/60 )) $(( $estremain%60 ))
done
printf "/ndone/n"
Esta es una técnica bastante fácil:
(simplemente reemplace sleep 20
con el comando que quiera indicar que se está ejecutando)
#!/bin/bash
sleep 20 & PID=$! #simulate a long process
echo "THIS MAY TAKE A WHILE, PLEASE BE PATIENT WHILE ______ IS RUNNING..."
printf "["
# While process is running...
while kill -0 $PID 2> /dev/null; do
printf "▓"
sleep 1
done
printf "] done!"
La salida se ve así:
> THIS MAY TAKE A WHILE, PLEASE BE PATIENT WHILE ______ IS RUNNING...
> [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] done!
Agrega un ▓
(densidad alta punteada) cada segundo hasta que el proceso se completa.