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?

23voto

F. Hauri Punkte 57640

Hires (Fließkomma) Fortschrittsbalken

Präambel

Entschuldigung für diese nicht so kurz Antwort.

Für Ungeduldige: Bitte testen Sie den Code unter Tun Sie es jetzt! (in der Mitte), mit Letzte animierte Demo (gegen Ende dieses Artikels.) und Praktisches Beispiel (am Ende).

Alle Demos hier verwenden read -t <float seconds> && break anstelle von sleep . Alle Schleifen können also durch Drücken von Return Schlüssel.

Einführung

Noch ein Bash-Fortschrittsbalken...

Da es hier bereits viele Antworten gibt, möchte ich noch einige Hinweise hinzufügen über Leistungen y Präzision .

1. Vermeiden Sie Gabeln!

Da ein Fortschrittsbalken dazu gedacht ist, zu laufen, während andere Prozesse arbeiten, muss es sich um eine schön Prozess...

Vermeiden Sie also die Verwendung von Gabeln wenn nicht benötigt. Beispiel: anstelle von

mysmiley=$(printf '%b' \\U1F60E)

Verwenden Sie

printf -v mysmiley '%b' \\U1F60E

Erläuterung: Wenn Sie var=$(command) initiieren Sie einen neuen Prozess zur Ausführung command und schickt seine Ausgabe zu variabel $var sobald sie beendet ist. Dies ist sehr ressourcenintensiv. Bitte vergleichen Sie:

TIMEFORMAT="%R"
time for ((i=2500;i--;)){ mysmiley=$(printf '%b' \\U1F60E);}
2.292
time for ((i=2500;i--;)){ printf -v mysmiley '%b' \\U1F60E;}
0.017
bc -l <<<'2.292/.017'
134.82352941176470588235

Auf meinem Host, gleiche Arbeit der Zuweisung $mysmiley (nur 2500 mal), scheinen ~135x langsamer/teurer zu sein, wenn man Gabel als durch die Verwendung eingebauter printf -v .

Dann

echo $mysmiley 

Also Ihr function nicht drucken müssen (oder Ausgabe ) alles. Ihre Funktion muss seine Antwort auf eine variabel .

2. Ganzzahl als Pseudo-Gleitkomma verwenden

Hier ist eine sehr kleine und schnelle Funktion zum Berechnen von Prozenten aus ganzen Zahlen, mit ganzer Zahl und Antwort eine Pseudo-Fließkommazahl:

percent(){
    local p=00$(($1*100000/$2))
    printf -v "$3" %.2f ${p::-3}.${p: -3}
}

Verwendung:

# percent <integer to compare> <reference integer> <variable name>
percent 33333 50000 testvar
printf '%8s%%\n' "$testvar"
   66.67%

3. Hires Konsolengrafik mit UTF-8:

Um diese Zeichen mit der Bash darzustellen, könnten Sie:

printf -v chars '\\U258%X ' {15..8}
printf "$chars\\n"

Dann müssen wir 8x verwenden string with como graphic width .

Tun Sie es jetzt!

Diese Funktion heißt percentBar weil es einen Balken aus einem Argument macht, das in Prozenten (fließend) eingegeben wurde:

percentBar ()  { 
    local prct totlen=$((8*$2)) lastchar barstring blankstring;
    printf -v prct %.2f "$1"
    ((prct=10#${prct/.}*totlen/10000, prct%8)) &&
        printf -v lastchar '\\U258%X' $(( 16 - prct%8 )) ||
            lastchar=''
    printf -v barstring '%*s' $((prct/8)) ''
    printf -v barstring '%b' "${barstring// /\\U2588}$lastchar"
    printf -v blankstring '%*s' $(((totlen-prct)/8)) ''
    printf -v "$3" '%s%s' "$barstring" "$blankstring"
}

Verwendung:

# percentBar <float percent> <int string width> <variable name>
percentBar 42.42 $COLUMNS bar1
echo "$bar1"

Um kleine Unterschiede aufzuzeigen:

percentBar 42.24 $COLUMNS bar2
printf "%s\n" "$bar1" "$bar2"

Mit Farben

Da die gerenderte Variable eine Zeichenkette mit fester Breite ist, ist die Verwendung von Farbe einfach:

percentBar 72.1 24 bar
printf 'Show this: \e[44;33;1m%s\e[0m at %s%%\n' "$bar" 72.1

Bar with color

Kleine Animation:

for i in {0..10000..33} 10000;do i=0$i
    printf -v p %0.2f ${i::-2}.${i: -2}
    percentBar $p $((COLUMNS-9)) bar
    printf '\r|%s|%6.2f%%' "$bar" $p
    read -srt .002 _ && break    # console sleep avoiding fork
done

||100.00%

clear; for i in {0..10000..33} 10000;do i=0$i
     printf -v p %0.2f ${i::-2}.${i: -2}
     percentBar $p $((COLUMNS-7)) bar
     printf '\r\e[47;30m%s\e[0m%6.2f%%' "$bar" $p
     read -srt .002 _ && break
done

PercentBar animation

Letzte animierte Demo

Eine weitere Demo mit verschiedenen Größen und farbigen Ausgaben:

printf '\n\n\n\n\n\n\n\n\e[8A\e7'&&for i in {0..9999..99} 10000;do 
    o=1 i=0$i;printf -v p %0.2f ${i::-2}.${i: -2}
    for l in 1 2 3 5 8 13 20 40 $((COLUMNS-7));do
        percentBar $p $l bar$((o++));done
    [ "$p" = "100.00" ] && read -rst .8 _;printf \\e8
    printf '%s\e[48;5;23;38;5;41m%s\e[0m%6.2f%%%b' 'In 1 char width: ' \
        "$bar1" $p ,\\n 'with 2 chars: ' "$bar2" $p ,\\n 'or 3 chars: ' \
        "$bar3" $p ,\\n 'in 5 characters: ' "$bar4" $p ,\\n 'in 8 chars: ' \
        "$bar5" $p .\\n 'There are 13 chars: ' "$bar6" $p ,\\n '20 chars: '\
        "$bar7" $p ,\\n 'then 40 chars' "$bar8" $p \
        ', or full width:\n' '' "$bar9" $p ''
    ((10#$i)) || read -st .5 _; read -st .1 _ && break
done

Das könnte etwa so aussehen:

Last animation percentBar animation

Praktisch GNU/Linux Probe: sha1sum mit Fortschrittsbalken

Unter Linux finden Sie viele nützliche Informationen unter /proc Pseudo-Dateisystem, also unter Verwendung zuvor definierter Funktionen percentBar y percent hier ist sha1progress :

percent(){ local p=00$(($1*100000/$2));printf -v "$3" %.2f ${p::-3}.${p: -3};}
sha1Progress() { 
    local -i totsize crtpos cols=$(tput cols) sha1in sha1pid
    local sha1res percent prctbar
    exec {sha1in}< <(exec sha1sum -b - <"$1")
    sha1pid=$!
    read -r totsize < <(stat -Lc %s "$1")
    while ! read -ru $sha1in -t .025 sha1res _; do
        read -r _ crtpos < /proc/$sha1pid/fdinfo/0
        percent $crtpos $totsize percent
        percentBar $percent $((cols-8)) prctbar
        printf '\r\e[44;38;5;25m%s\e[0m%6.2f%%' "$prctbar" $percent;

    done
    printf "\r%s  %s\e[K\n" $sha1res "$1"
}

Ja, natürlich, 25 ms Timeout bedeutet ca. 40 Aktualisierungen pro Sekunde. Dies könnte overkill aussehen, aber gut funktionieren auf meinem Host, und sowieso, dies kann getunnelt werden.

sha1Progress sample

Erläuterung:

  • exec {sha1in}< eine neue Dateideskriptor für die Ausgabe von
  • <( ... ) Verzweigte Aufgabe läuft im Hintergrund
  • sha1sum -b - <"$1" sicherstellen, dass die Eingabe von STDIN ( fd/0 )
  • while ! read -ru $sha1in -t .025 sha1res _ Während keine Eingabe von der Unteraufgabe gelesen wird, wird in 25 ms ...
  • /proc/$sha1pid/fdinfo/0 Kernelvariable, die Informationen über Datei-Deskriptor 0 (STDIN) der Aufgabe $sha1pid

0 Stimmen

Schöne Antwort! In der ersten animierten Demo sehe ich den \r, der bewirkt, dass der Cursor zurückgesetzt wird, so dass sich die Leiste neu zeichnet, aber in der zweiten animierten Demo, wie erreichst du das?

1 Stimmen

@David 1. Zeile druckt 8 Zeilen, dann Esc[8A, um 8 Zeilen höher zurückzukehren. Dann Esc7 Speichert die Cursorposition... Dann Esc8 stellt die Cursorposition wieder her.

0 Stimmen

Das ist cool, das ist so erstaunlich.

23voto

polle Punkte 187

APT-Stil Fortschrittsbalken (unterbricht nicht die normale Ausgabe)

Gib eine Bildbeschreibung hier ein

BEARBEITEN: Für eine aktualisierte Version besuchen Sie meine GitHub-Seite

Ich war nicht zufrieden mit den Antworten auf diese Frage. Was ich persönlich suchte, war ein schicker Fortschrittsbalken wie bei APT.

Ich habe mir den C-Quellcode für APT angesehen und beschlossen, mein eigenes Äquivalent für bash zu schreiben.

Dieser Fortschrittsbalken bleibt schön am unteren Rand des Terminals und beeinträchtigt keine Ausgabe, die an das Terminal gesendet wird.

Bitte beachten Sie, dass der Balken derzeit fest auf 100 Zeichen Breite eingestellt ist. Wenn Sie ihn auf die Größe des Terminals skalieren möchten, ist dies ebenfalls recht einfach zu erreichen (Die aktualisierte Version auf meiner GitHub-Seite kommt damit gut zurecht).

Ich werde mein Skript hier posten. Verwendungsbeispiel:

quelle ./progress_bar.sh
echo "Dies ist eine Ausgabe"
setup_scroll_area
sleep 1
echo "Dies ist eine Ausgabe 2"
draw_progress_bar 10
sleep 1
echo "Dies ist eine Ausgabe 3"
draw_progress_bar 50
sleep 1
echo "Dies ist eine Ausgabe 4"
draw_progress_bar 90
sleep 1
echo "Dies ist eine Ausgabe 5"
destroy_scroll_area

Das Skript (Ich empfehle dringend die Version auf meiner GitHub-Seite):

#!/bin/bash

# Dieser Code wurde von dem Open-Source-C-Code des APT Fortschrittsbalkens inspiriert
# http://bazaar.launchpad.net/~ubuntu-branches/ubuntu/trusty/apt/trusty/view/head:/apt-pkg/install-progress.cc#L233

#
# Verwendung:
# Dieses Skript einbinden
# 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
    # Ein wenig nach unten scrollen, um visuelle Glitches zu vermeiden, wenn der Bildschirmbereich um eine Zeile verkleinert wird
    echo -en "\n"

    # Cursor speichern
    echo -en "$CODE_SAVE_CURSOR"
    # Bildlaufbereich festlegen (dadurch wird der Cursor oben links platziert)
    echo -en "\033[0;${lines}r"

    # Cursor wiederherstellen, aber sicherstellen, dass er sich im Bildlaufbereich befindet
    echo -en "$CODE_RESTORE_CURSOR"
    echo -en "$CODE_CURSOR_IN_SCROLL_AREA"

    # Leerer Fortschrittsbalken starten
    draw_progress_bar 0
}

function destroy_scroll_area() {
    lines=$(tput lines)
    # Cursor speichern
    echo -en "$CODE_SAVE_CURSOR"
    # Bildlaufbereich festlegen (dadurch wird der Cursor oben links platziert)
    echo -en "\033[0;${lines}r"

    # Cursor wiederherstellen, aber sicherstellen, dass er sich im Bildlaufbereich befindet
    echo -en "$CODE_RESTORE_CURSOR"
    echo -en "$CODE_CURSOR_IN_SCROLL_AREA"

    # Wir sind fertig, also den Scrollbalken löschen
    clear_progress_bar

    # Ein wenig nach unten scrollen, um visuelle Glitches zu vermeiden, wenn der Bildschirmbereich um eine Zeile wächst
    echo -en "\n\n"
}

function draw_progress_bar() {
    percentage=$1
    lines=$(tput lines)
    let lines=$lines
    # Cursor speichern
    echo -en "$CODE_SAVE_CURSOR"

    # Cursorposition zur letzten Zeile verschieben
    echo -en "\033[${lines};0f"

    # Fortschrittsbalken löschen
    tput el

    # Fortschrittsbalken zeichnen
    print_bar_text $percentage

    # Cursorposition wiederherstellen
    echo -en "$CODE_RESTORE_CURSOR"
}

function clear_progress_bar() {
    lines=$(tput lines)
    let lines=$lines
    # Cursor speichern
    echo -en "$CODE_SAVE_CURSOR"

    # Cursorposition zur letzten Zeile verschieben
    echo -en "\033[${lines};0f"

    # Fortschrittsbalken löschen
    tput el

    # Cursorposition wiederherstellen
    echo -en "$CODE_RESTORE_CURSOR"
}

function print_bar_text() {
    local percentage=$1

    # Fortschrittsbalken vorbereiten
    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 "]");

    # Fortschrittsbalken ausgeben
    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}"
}

0 Stimmen

Perfekt! Genau das habe ich gesucht.

1 Stimmen

Vermeiden Sie Gabeln!! Schreiben Sie nicht var=$(printf...), sondern printf -v var ..., kein var=$(echo -n ...;printf) sondern printf -v var ...; var=...${var}...

1 Stimmen

DAS! Das sind die Waren, nach denen ich gesucht habe. Ich möchte nicht lernen, wie man "\r" verwendet, um eine Zeile neu zu zeichnen, ich möchte sehen, wie man über einen Abschnitt des Bildschirms zeichnet! Bravo!

19voto

Wojtek Punkte 197

GNU tar hat eine nützliche Option, die eine Funktionalität einer einfachen Fortschrittsanzeige bietet.

(...) Eine weitere verfügbare Checkpoint-Aktion ist 'dot' (oder '.'). Es fordert tar auf, einen einzelnen Punkt auf dem standardmäßigen Auflistungs-Stream auszugeben, z. B.:

$ tar -c --checkpoint=1000 --checkpoint-action=dot /var
...

Derselbe Effekt kann erzielt werden durch:

$ tar -c --checkpoint=.1000 /var

1 Stimmen

+1 für den einfachsten Ansatz! Wenn keine Punkte gedruckt werden, versuchen Sie, die Anzahl zu verringern, zum Beispiel --checkpoint=.10. Es funktioniert auch großartig beim Extrahieren mit tar -xz.

12voto

Vagiz Duseev Punkte 357

So könnte es aussehen

Eine Datei hochladen

[##################################################] 100% (137921 / 137921 bytes)

Warten, bis ein Job abgeschlossen ist

[#########################                         ] 50% (15 / 30 Sekunden)

Einfache Funktion, die es umsetzt

Sie können es einfach in Ihr Skript kopieren. Es erfordert nichts weiter, um zu funktionieren.

PROGRESS_BAR_WIDTH=50  # Länge der Fortschrittsanzeige in Zeichen

draw_progress_bar() {
  # Argumente: aktueller Wert, maximaler Wert, Maßeinheit (optional)
  local __value=$1
  local __max=$2
  local __unit=${3:-""}  # Wenn keine Maßeinheit angegeben ist, nicht anzeigen

  # Prozent berechnen
  if (( $__max < 1 )); then __max=1; fi  # Schutz vor Null-Division
  local __percentage=$(( 100 - ($__max*100 - $__value*100) / $__max ))

  # Die Anzeige entsprechend der Breite der Fortschrittsanzeige anpassen
  local __num_bar=$(( $__percentage * $PROGRESS_BAR_WIDTH / 100 ))

  # Fortschrittsanzeige zeichnen
  printf "["
  for b in $(seq 1 $__num_bar); do printf "#"; done
  for s in $(seq 1 $(( $PROGRESS_BAR_WIDTH - $__num_bar ))); do printf " "; done
  printf "] $__percentage%% ($__value / $__max $__unit)\r"
}

Beispiel zur Verwendung

Hier laden wir eine Datei hoch und zeichnen die Fortschrittsanzeige bei jeder Iteration neu. Es spielt keine Rolle, welcher Job tatsächlich ausgeführt wird, solange wir 2 Werte erhalten können: maximaler Wert und aktueller Wert.

Im folgenden Beispiel ist der maximale Wert file_size und der aktuelle Wert wird von einer Funktion bereitgestellt und heißt uploaded_bytes.

# Eine Datei hochladen
file_size=137921

while true; do
  # Aktuellen Wert der hochgeladenen Bytes erhalten
  uploaded_bytes=$(some_function_that_reports_progress)

  # Eine Fortschrittsanzeige zeichnen
  draw_progress_bar $uploaded_bytes $file_size "bytes"

  # Überprüfen, ob wir 100% erreicht haben
  if [ $uploaded_bytes == $file_size ]; then break; fi
  sleep 1  # Warten, bevor neu gezeichnet wird
done
# Zum Zeilenumbruch am Ende des Uploads gehen
printf "\n"

0 Stimmen

Saubere und einfache Funktion. Vielen Dank!

0 Stimmen

Das ist wonach ich suche! Vielen Dank :)

6voto

Dustin Michels Punkte 2431

Ich brauchte eine Fortschrittsleiste, um über die Zeilen in einer CSV-Datei zu iterieren. Ich konnte cprn's Code anpassen und für mich nützlich machen:

BAR='##############################'
FILL='------------------------------'
totalLines=$(wc -l $file | awk '{print $1}')  # Anzahl der Zeilen in der Datei
barLen=30

# --- Über die Zeilen in der CSV-Datei iterieren ---
count=0
while IFS=, read -r _ col1 col2 col3; do
    # Fortschrittsleiste aktualisieren
    count=$(($count + 1))
    percent=$((($count * 100 / $totalLines * 100) / 100))
    i=$(($percent * $barLen / 100))
    echo -ne "\r[${BAR:0:$i}${FILL:$i:barLen}] $count/$totalLines ($percent%)"

    # andere Dinge
    (...)
done <$file

Sieht so aus:

[##----------------------------] 17128/218210 (7%)

0 Stimmen

Danke für die Lösung! Hat genau so funktioniert, wie ich es brauchte.

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