537 Stimmen

Wie fügt man einer Shell-Skript eine Fortschrittsleiste hinzu?

Beim Skripten in bash oder einer anderen Shell in *NIX, wenn ein Befehl ausgeführt wird, der länger als ein paar Sekunden dauern wird, wird eine Fortschrittsanzeige benötigt.

Zum Beispiel das Kopieren einer großen Datei oder das Öffnen einer großen TAR-Datei.

Welche Möglichkeiten empfehlen Sie, um Fortschrittsanzeigen zu Shell-Skripts hinzuzufügen?

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?

5voto

WinEunuuchs2Unix Punkte 1431

Ich brauchte eine Fortschrittsanzeige, die in einer Popup-Benachrichtigungsnachricht (notify-send) passt, um den TV-Lautstärkepegel darzustellen. Vor kurzem habe ich einen Musikplayer in Python geschrieben und das TV-Bild ist die meiste Zeit ausgeschaltet.

Beispielausgabe aus dem Terminal

test_progress_bar3.gif


Bash-Skript

#!/bin/bash

# Zeigt eine Fortschrittsanzeige bei Schrittnummer $1 (von 0 bis 100)

function is_int() { test "$@" -eq "$@" 2> /dev/null; } 

# Parameter 1 muss eine ganze Zahl sein
if ! is_int "$1" ; then
   echo "Keine ganze Zahl: ${1}"
   exit 1
fi

# Parameter 1 muss >= 0 und <= 100 sein
if [ "$1" -ge 0 ] && [ "$1" -le 100 ]  2>/dev/null
then
    :
else
    echo Ungültige Lautstärke: ${1}
    exit 1
fi

# Hauptfunktion, die für schnelles Kopieren in ein anderes Programm entwickelt wurde
Main () {

    Bar=""                      # Fortschrittsanzeige / Lautstärkepegel
    Len=25                      # Länge der Fortschrittsanzeige / Lautstärkepegel
    Div=4                       # Teiler in die Lautstärke für # der Blöcke
    Fill=""                    # Auffüllen bis $Len
    Arr=( "" "" "" "" )     # UTF-8 linken Blöcke: 7/8, 1/4, 1/2, 3/4

    FullBlock=$((${1} / Div))   # Anzahl voller Blöcke
    PartBlock=$((${1} % Div))   # Größe des teilweisen Blocks (Arrayindex)

    while [[ $FullBlock -gt 0 ]]; do
        Bar="$Bar${Arr[0]}"     # Fügt eine vollständige Block in die Fortschrittsanzeige ein
        (( FullBlock-- ))       # Zähler für volle Blöcke verringern
    done

    # Wenn Rest null ist, kein teilweiser Block, sonst Zeichen aus Array anhängen
    if [[ $PartBlock -gt 0 ]]; then
        Bar="$Bar${Arr[$PartBlock]}"
    fi

    while [[ "${#Bar}" -lt "$Len" ]]; do
        Bar="$Bar$Fill"         # Fortschrittsanzeige mit Füllzeichen auffüllen
    done

    echo Lautstärke: "$1 $Bar"
    exit 0                      # Diese Zeile entfernen, wenn sie in ein Programm kopiert wird
} # Main

Main "$@"

Test Bash-Skript

Verwenden Sie dieses Skript, um die Fortschrittsanzeige im Terminal zu testen.

#!/bin/bash

# test_progress_bar3

Main () {

    tput civis                              # Cursor ausschalten
    for ((i=0; i<=100; i++)); do
        CurrLevel=$(./progress_bar3 "$i")   # Fortschrittsanzeige von 0 bis 100 generieren
        echo -ne "$CurrLevel"\\r            # Über dem gleichen Zeile erneut drucken
        sleep .04
    done
    echo -e \\n                             # Zeile weitermachen, um den letzten Fortschritt beizubehalten
    echo "$0 Fertig"
    tput cnorm                              # Cursor wieder einschalten
} # Main

Main "$@"

Zusammenfassung

in diesem Abschnitt wird erläutert, wie notify-send verwendet wird, um schnell Popup-Benachrichtigungsnachrichten auf den Desktop zu senden. Dies ist erforderlich, da sich der Lautstärkepegel viele Male pro Sekunde ändern kann und das Standardverhalten der Bubble-Nachrichten ist, dass eine Nachricht viele Sekunden auf dem Desktop bleibt.

Beispiel Popup-Benachrichtigungsnachricht

tvpowered.gif

Bash-Code für Popup-Benachrichtigungsnachricht

Aus dem obigen Skript wurde die main-Funktion in eine neue Funktion namens VolumeBar in einem vorhandenen Bash-Skript namens tvpowered kopiert. Der Befehl exit 0 in der kopierten main-Funktion wurde entfernt.

So wird es aufgerufen und der notify-send-Befehl von Ubuntu darüber informiert, dass wir Popup-Benachrichtungsnachrichten senden werden:

VolumeBar $CurrVolume
# Ask Ubuntu: https://askubuntu.com/a/871207/307523
notify-send --urgency=critical "tvpowered" \
    -h string:x-canonical-private-synchronous:volume \
    --icon=/usr/share/icons/gnome/48x48/devices/audio-speakers.png \
    "Lautstärke: $CurrVolume $Bar"

Dies ist die neue Zeile, die notify-send angibt, die letzte Popup-Benachrichtigungsnachricht sofort zu ersetzen:

-h string:x-canonical-private-synchronous:volume \

volume gruppiert die Popup-Benachrichtigungsnachrichten zusammen und neue Nachrichten in dieser Gruppe ersetzen sofort die vorherigen. Anstelle von volume können Sie auch anything verwenden.

4voto

Noah Spurrier Punkte 478

Meine Lösung zeigt den Prozentsatz des Tarballs an, der gerade entpackt und geschrieben wird. Ich benutze dies, wenn ich 2GB-Root-Dateisystemabbilder schreibe. Bei solchen Dingen braucht man wirklich eine Fortschrittsanzeige. Was ich mache, ist gzip --list zu verwenden, um die insgesamt entpackte Größe des Tarballs zu erhalten. Daraus berechne ich den Blockierfaktor, der benötigt wird, um die Datei in 100 Teile aufzuteilen. Schließlich gebe ich für jeden Block eine Zwischenmeldung aus. Bei einer 2GB-Datei ergibt das ungefähr 10MB pro Block. Wenn das zu groß ist, können Sie den BLOCKING_FACTOR durch 10 oder 100 teilen, aber dann ist es schwieriger, ein hübsches Ergebnis in Form eines Prozentsatzes auszugeben.

Angenommen, Sie verwenden Bash, dann können Sie die folgende Shell-Funktion nutzen

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}
}

0 Stimmen

Schöne Lösung, aber wie macht man das, wenn man ein Verzeichnis komprimieren möchte?

4voto

thedk Punkte 1871

Zum ersten ist Bar nicht der einzige Fortschrittszähler für Pipes. Ein anderer (vielleicht sogar bekannterer) ist pv (Pipe Viewer).

Zweitens können Bar und pv zum Beispiel so verwendet werden:

$ bar datei1 | wc -l 
$ pv datei1 | wc -l

oder sogar:

$ tail -n 100 datei1 | bar | wc -l
$ tail -n 100 datei1 | pv | wc -l

Ein nützlicher Trick, wenn Sie bar und pv in Befehlen verwenden möchten, die mit Dateien als Argumenten arbeiten, z.B. beim Kopieren von datei1 datei2, ist die Verwendung von Prozess-Substitution:

$ copy <(bar datei1) datei2
$ copy <(pv datei1) datei2

Prozess-Substitution ist eine Art bash-Magie, die temporäre FIFO-Pipe-Dateien /dev/fd/ erstellt und die Standardausgabe des ausgeführten Prozesses (innerhalb der Klammern) durch diese Pipe verbindet. Das kopieren sieht sie dann wie eine gewöhnliche Datei (mit einer Ausnahme, es kann sie nur vorwärts lesen).

Update:

Der Bar-Befehl erlaubt auch das Kopieren. Laut der Bar-Manpage:

bar --in-datei /dev/rmt/1cbn --out-datei \
     bandwiederherstellung.tar --größe 2,4g --puffergröße 64k

Aber meiner Meinung nach ist die Prozess-Substitution der generischere Weg, dies zu tun. Und es verwendet das Programm cp selbst.

4voto

casper.dcl Punkte 9586

Viele Antworten beschreiben das Schreiben eigener Befehle zum Drucken von '\r' + $some_sort_of_progress_msg. Das Problem ist manchmal, dass das Drucken Hunderte dieser Updates pro Sekunde den Prozess verlangsamen kann.

Wenn jedoch einer Ihrer Prozesse Ausgaben erzeugt (z. B. 7z a -r newZipFile myFolder gibt jeden Dateinamen aus, während er sie komprimiert), dann existiert eine einfachere, schnellere, schmerzlose und anpassbare Lösung.

Installieren Sie das Python-Modul tqdm.

$ sudo pip install tqdm
$ # jetzt viel Spaß haben
$ 7z a -r -bd newZipFile myFolder | tqdm >> /dev/null
$ # wenn wir das erwartete Gesamtvolumen kennen, können wir eine Leiste haben!
$ 7z a -r -bd newZipFile myFolder | grep -o Compressing | tqdm --total $(find myFolder -type f | wc -l) >> /dev/null

Hilfe: tqdm -h. Ein Beispiel mit mehr Optionen:

$ find / -name '*.py' -exec cat \{} \; | tqdm --unit loc --unit_scale True | wc -l

Als Bonus können Sie tqdm auch verwenden, um Iterables im Python-Code zu umschließen.

https://github.com/tqdm/tqdm/blob/master/README.rst#module

0 Stimmen

Ich denke nicht, dass dein Beispiel mit "mehr Optionen" funktioniert. Es scheint, dass tqdm STDOUT an wc -l über ein Rohr weitergeleitet wird. Wahrscheinlich möchtest du das maskieren.

1 Stimmen

@cprn tqdm zeigt den Fortschritt auf STDERR an, während es seine Eingabe über STDIN an STDOUT weiterleitet. In diesem Fall würde wc -l dieselbe Eingabe erhalten, als ob tqdm nicht enthalten wäre.

0 Stimmen

Ah, jetzt ergibt es Sinn. Danke für die Erklärung.

3voto

Ich ziehe es vor, dialog mit dem --gauge Parameter zu verwenden. Wird sehr oft bei der Installation von .deb-Paketen und anderen grundlegenden Konfigurationen vieler Distributionen verwendet. Also musst du das Rad nicht neu erfinden... wieder

Gib einfach einen int-Wert von 1 bis 100 @stdin ein. Ein einfaches und albernes Beispiel:

for a in {1..100}; do sleep .1s; echo $a| dialog --gauge "warte" 7 30; done

Ich habe diese Datei /bin/Wait (mit chmod u+x-Berechtigungen) für Kochzwecke :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 Sekunden übrig ($MLEFT Minuten)";
    TITLE="Warten auf $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="Warten beendet: $1";audio="Bereit";
else msg=$2;audio=$2;fi 

/usr/bin/notify-send --icon=stock_appointment-reminder-excl "$msg"
espeak -v spanish "$audio"

Also kann ich folgendes eingeben:

Wait "34 min" "den Ofen vorheizen"

oder

Wait "31. Dez" "Frohes neues Jahr"

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X