En utilisant un script bash uniquement, comment pouvez-vous fournir un indicateur de progression bash?
Je peux donc exécuter une commande sous la forme bash et, pendant l'exécution de cette commande, informer l'utilisateur qu'il se passe toujours quelque chose.
Dans cet exemple, à l'aide de SCP, je montre comment récupérer l'ID de processus (pid), puis effectuer une opération en cours d'exécution.
Cela affiche une simple icône de spinnng.
/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 solution 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
Si vous disposez d'un moyen d'estimer le pourcentage effectué, tel que le nombre actuel de fichiers traités et le nombre total, vous pouvez créer un indicateur de progression linéaire simple avec un peu de calcul et des hypothèses sur la largeur de l'écran.
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
Ou au lieu d'un mètre linéaire, vous pouvez estimer le temps restant. C'est à peu près aussi précis que d'autres choses similaires.
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"
Reporté de here est une fonction spinner de Nice (avec une légère modification), aidera votre curseur à rester dans la position originale également.
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"
}
avec usage:
(a_long_running_task) &
spinner
Ceci est une technique assez facile:
(remplacez simplement sleep 20
par la commande que vous voulez indiquer en cours d'exécution)
#!/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 sortie ressemble à ceci:
> THIS MAY TAKE A WHILE, PLEASE BE PATIENT WHILE ______ IS RUNNING...
> [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] done!
Il ajoute un ▓
(haute densité en pointillé) toutes les secondes jusqu'à la fin du processus.
Voici un simple onliner, que j'utilise:
while true; do for X in '-' '/' '|' '\'; do echo -en "\b$X"; sleep 0.1; done; done
Voici ma tentative. Je suis nouveau dans les scripts bash, donc une partie de ce code peut être terrible :)
Exemple de sortie:
Le code:
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
Vous pouvez voir la source ici: https://Gist.github.com/F1LT3R/fa7f102b08a514f2c535
En plus du disque classique, vous pouvez utiliser cette barre de progression
Il atteint la précision des sous-caractères en utilisant demi-caractères
Code inclus sur le lien.
Voici un exemple d'indicateur d'activité pour un test de vitesse de connexion Internet via la commande linux 'speedtest-cli':
printf '\n\tInternet speed test: '
# http://stackoverflow.com/questions/12498304/using-bash-to-display-a-progress-working-indicator
spin[0]="-"
spin[1]="\\"
spin[2]="|"
spin[3]="/"
# http://stackoverflow.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
Mise à jour - exemple:
Barre de progression psychédélique pour les scripts bash. Appelez par la ligne de commande sous la forme "./progressbar x y", où "x" correspond à une durée en secondes et "y" à un message à afficher. La fonction interne progressbar () fonctionne également de manière autonome et prend "x" en tant que pourcentage et "y" en tant que message.
#!/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"
Les commentaires de @DavidD sur la réponse de Pez Cuckows sont un exemple de la manière dont vous pouvez capturer la sortie de la barre de progression dans un script tout en maintenant la visionneuse à l'écran:
#!/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
J'ai prolongé la réponse de somme de contrôle dans sa réponse en affichant un message d'information variable après le compteur:
#!/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..."
Je n'aime pas cela si la sortie stdout avec un spinner est redirigée vers un fichier, less
affiche ^H
pour chaque retour arrière au lieu de les éviter dans une sortie de fichier. Est-ce possible avec un spinner facile comme celui-ci?