Habe nicht gesehen, etwas ähnliches und alle benutzerdefinierten Funktionen hier scheinen auf Rendering allein so zu konzentrieren... meine sehr einfache POSIX-konforme Lösung unten mit Schritt für Schritt Erklärungen, weil diese Frage nicht trivial ist.
TL;DR
Das Rendern des Fortschrittsbalkens ist sehr einfach. Abzuschätzen, wie viel davon gerendert werden soll, ist eine andere Sache. So wird der Fortschrittsbalken gerendert (animiert) - Sie können dieses Beispiel kopieren und in eine Datei einfügen und ausführen:
#!/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}
- Werte von 1 bis 20
echo
- zum Terminal drucken (d.h. zu stdout
)
echo -n
- Druck ohne neue Zeile am Ende
echo -e
- Sonderzeichen beim Drucken interpretieren
"\r"
- Wagenrücklauf, ein spezielles Zeichen, um zum Anfang der Zeile zurückzukehren
Man kann jeden Inhalt mit beliebiger Geschwindigkeit rendern, so dass diese Methode sehr universell ist, z.B. wird sie oft für die Visualisierung von "Hacking" in albernen Filmen verwendet, kein Scherz.
Vollständige Antwort (von Null bis zum Arbeitsbeispiel)
Das eigentliche Problem besteht darin, wie man die $i
Wert, d.h. wie viel vom Fortschrittsbalken angezeigt werden soll. In dem obigen Beispiel habe ich es einfach inkrementieren lassen for
Schleife, um das Prinzip zu veranschaulichen, aber eine reale Anwendung würde eine Endlosschleife verwenden und die $i
Variable bei jeder Iteration. Für diese Berechnung werden folgende Bestandteile benötigt:
- wie viel Arbeit noch zu tun ist
- wie viel Arbeit bisher geleistet wurde
Im Falle von cp
benötigt er die Größe einer Quelldatei und die Größe der Zieldatei:
#!/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
foo=$(bar)
- laufen bar
in einem Unterprozess und speichern dessen stdout
a $foo
stat
- Dateistatistiken drucken auf stdout
stat -c
- einen formatierten Wert drucken
%s
- Format für die Gesamtgröße
Bei Vorgängen wie dem Entpacken von Dateien ist die Berechnung der Quellgröße etwas schwieriger, aber immer noch so einfach wie die Ermittlung der Größe einer unkomprimierten Datei:
#!/bin/sh
src_size=$(gzip -l "$src" | tail -n1 | tr -s ' ' | cut -d' ' -f3)
gzip -l
- Info über Zip-Archiv drucken
tail -n1
- mit 1 Zeile von unten arbeiten
tr -s ' '
- mehrere Leerzeichen in ein einziges übersetzen ("quetschen")
cut -d' ' -f3
- 3. durch Leerzeichen getrenntes Feld (Spalte) ausschneiden
Hier liegt der Kern des Problems, das ich bereits erwähnt habe. Diese Lösung ist immer weniger allgemein. Alle Berechnungen des tatsächlichen Fortschritts sind eng an die Domäne gebunden, die Sie zu visualisieren versuchen, sei es eine einzelne Dateioperation, ein Timer-Countdown, eine steigende Anzahl von Dateien in einem Verzeichnis, eine Operation an mehreren Dateien usw., daher können sie nicht wiederverwendet werden. Der einzige wiederverwendbare Teil ist das Rendern des Fortschrittsbalkens. Um ihn wiederzuverwenden, müssen Sie ihn abstrahieren und in einer Datei speichern (z. B. /usr/lib/progress_bar.sh
), dann definieren Sie Funktionen, die für Ihren Bereich spezifische Eingabewerte berechnen. So könnte ein verallgemeinerter Code aussehen (ich habe auch die $BAR
dynamisch, weil die Leute danach gefragt haben, der Rest sollte inzwischen klar sein):
#!/bin/bash
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
- ein Builtin zum Drucken von Daten in einem bestimmten Format
printf %50s
- nichts drucken, sondern mit 50 Leerzeichen auffüllen
tr ' ' '#'
- jedes Leerzeichen in ein Rautenzeichen übersetzen
Und so würden Sie es verwenden:
#!/bin/bash
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
Natürlich können Sie dies in eine Funktion verpacken, umschreiben, um mit gepipten Streams zu arbeiten, die geforkete Prozess-ID mit $!
und übergibt sie an progress_bar.sh
so könnte es erraten wie man die zu leistende Arbeit und die geleistete Arbeit berechnet, was auch immer Ihr Gift ist.
Nebenbemerkungen
Zu diesen beiden Dingen werde ich am häufigsten befragt:
${}
: In den obigen Beispielen verwende ich ${foo:A:B}
. Der Fachbegriff für diese Syntax lautet Parameter Erweiterung eine eingebaute Shell-Funktionalität, die es erlaubt, eine Variable (Parameter) zu manipulieren, z. B. eine Zeichenkette mit :
aber auch andere Dinge zu tun - es wird keine Subshell erzeugt. Die prominenteste Beschreibung der Parametererweiterung, die mir einfällt (die nicht vollständig POSIX-kompatibel ist, aber den Leser das Konzept gut verstehen lässt), ist in der man bash
Seite.
$()
: In den obigen Beispielen verwende ich foo=$(bar)
. Es erzeugt eine separate Shell in einem Unterprozess (auch bekannt als Unterschale ), läuft die bar
Befehl und weist seine Standardausgabe einem $foo
variabel. Es ist nicht dasselbe wie Prozess-Substitution und es ist etwas ganz anderes als Rohr ( |
). Am wichtigsten ist, dass es funktioniert. Manche sagen, dass dies vermieden werden sollte, weil es langsam ist. Ich behaupte, dass dies hier "in Ordnung" ist, denn was immer dieser Code zu visualisieren versucht, dauert lange genug, um einen Fortschrittsbalken zu benötigen. Mit anderen Worten: Subshells sind nicht der Engpass. Der Aufruf einer Subshell erspart mir auch die Mühe, zu erklären, warum return
ist nicht das, wofür die meisten Menschen es halten, was ist ein Beenden Status und warum die Übergabe von Werten aus Funktionen in Shells nicht das ist, wozu Shell-Funktionen im Allgemeinen gut sind. Um mehr über all das herauszufinden, empfehle ich wiederum die man bash
Seite.
Fehlersuche
Wenn Ihre Shell tatsächlich sh anstelle von bash ausführt, oder eine wirklich alte bash, wie z.B. Standard-OSX, dann kann es sein, dass sie sich bei echo -ne "\r${BAR:0:$i}"
. Der genaue Fehler ist Bad substitution
. Wenn dies bei Ihnen der Fall ist, können Sie stattdessen im Kommentarbereich Folgendes angeben echo -ne "\r$(expr "x$name" : "x.\{0,$num_skip\}\(.\{0,$num_keep\}\)")"
um einen portableren Posix-kompatiblen / weniger lesbaren Teilstring-Abgleich durchzuführen.
Ein vollständiges, funktionierendes /bin/sh-Beispiel:
#!/bin/sh
src=100
tgt=0
get_work_todo() {
echo $src
}
do_work() {
echo "$(( $1 + 1 ))"
}
BAR_length=50
BAR_character='#'
BAR=$(printf %${BAR_length}s | tr ' ' $BAR_character)
work_todo=$(get_work_todo) # how much there is to do
work_done=0
while true; do
work_done="$(do_work $work_done)"
i=$(( $work_done * $BAR_length / $work_todo ))
n=$(( $BAR_length - $i ))
printf "\r$(expr "x$BAR" : "x.\{0,$n\}\(.\{0,$i\}\)")"
if [ $work_done = $work_todo ]; then
echo "\n"
break;
fi
sleep .1
done
0 Stimmen
Siehe auch stackoverflow.com/questions/12498304/… für Beispiele der Kontrolllogik (Hintergrund einer Aufgabe und etwas tun, bis sie beendet ist).
4 Stimmen
Es gibt eine Reihe von Anforderungen, die wir beim Skripten häufig nützlich finden. Protokollierung, Anzeige des Fortschritts, Farben, schicke Ausgaben usw... Ich habe immer das Gefühl gehabt, dass es irgendeine Art von einfachem Skript-Framework geben sollte. Schließlich habe ich beschlossen, eines zu implementieren, da ich keines finden konnte. Möglicherweise finden Sie dies hilfreich. Es ist rein Bash, ich meine nur Bash. github.com/SumuduLansakara/JustBash
0 Stimmen
Sollte dies nicht verschoben werden nach unix.stackexchange.com?
0 Stimmen
Ich benutze gerne
pv
für alles, was gepiped werden kann. Beispiel:ssh remote "cd /home/user/ && tar czf - accounts" | pv -s 23091k | tar xz