Как добавить индикатор выполнения в сценарий оболочки?


413

При выполнении сценариев в bash или любой другой оболочке в * NIX при выполнении команды, которая займет более нескольких секунд, требуется индикатор выполнения.

Например, копирование большого файла, открытие большого файла tar.

Какими способами вы рекомендуете добавлять индикаторы выполнения в сценарии оболочки?


См. Также stackoverflow.com/questions/12498304/… для примеров логики управления (справьтесь с заданием и сделайте что-нибудь, пока оно не закончится).
tripleee

1
Существует ряд требований, которые мы часто находим полезными при написании сценариев. ведение журнала, отображение прогресса, цвета, необычные результаты и т. д. Я всегда чувствовал, что должна быть какая-то простая среда сценариев. Наконец, я решил реализовать один, так как я не мог найти ни одного. Вы можете найти это полезным. Это в чистом Bash, я имею в виду Just Bash. github.com/SumuduLansakara/JustBash
Сумуду

Не следует ли перенести это на unix.stackexchange.com ?
Итан

Ответы:


685

Вы можете реализовать это, переписав строку. Используйте \rдля возврата к началу строки без записи \nв терминал.

Напишите, \nкогда вы закончите продвигать линию.

Использовать echo -neдля:

  1. не печатать \nи
  2. распознавать escape-последовательности, такие как \r.

Вот демо:

echo -ne '#####                     (33%)\r'
sleep 1
echo -ne '#############             (66%)\r'
sleep 1
echo -ne '#######################   (100%)\r'
echo -ne '\n'

В комментарии ниже, puk упоминает, что это «не получается», если вы начинаете с длинной строки, а затем хотите написать короткую строку: в этом случае вам нужно будет перезаписать длину длинной строки (например, пробелами).


23
В соответствии с man-страницей echo (по крайней мере, в MacOS X) sh / bash использует свою собственную встроенную команду echo , которая не принимает «-n» ... поэтому, чтобы выполнить то же самое, вам нужно поставить \ r \ с в конце строки, а не только \ г
Justin Jenkins

51
Портативный способ вывести это - использовать printfвместо echo.
Йенс

9
для printf нам пришлось бы использовать этот формат: printf "#### (50%%)\r"он не будет работать с одинарными кавычками, а знак процента необходимо экранировать.
Нуреттин

7
Я не понимаю это - принято и куча откликов за хак "Я угадаю, сколько времени эта операция займет на неизвестном оборудовании"? Пв это правильный ответ ИМО (но бар тоже подойдет)
Стивен

19
Вопрос был «Как мне сделать прогресс-бары» с примером копирования файлов. Я сосредоточился на проблеме «графики», а не на подсчете того, как далеко продвигается операция копирования файла.
Митч Хейл

73

Вы также можете быть заинтересованы в том, как сделать счетчик :

Могу ли я сделать спиннер в Bash?

Конечно!

i=1
sp="/-\|"
echo -n ' '
while true
do
    printf "\b${sp:i++%${#sp}:1}"
done

Каждый раз, когда цикл повторяется, он отображает следующий символ в строке sp, оборачиваясь по мере достижения конца. (i - позиция отображаемого текущего символа, а $ {# sp} - длина строки sp).

Строка \ b заменяется символом «возврат». Или вы можете поиграть с \ r, чтобы вернуться к началу строки.

Если вы хотите, чтобы он замедлился, поместите команду sleep в цикл (после printf).

POSIX-эквивалент будет:

sp='/-\|'
printf ' '
while true; do
    printf '\b%.1s' "$sp"
    sp=${sp#?}${sp%???}
done

Если у вас уже есть цикл, который выполняет много работы, вы можете вызвать следующую функцию в начале каждой итерации, чтобы обновить счетчик:

sp="/-\|"
sc=0
spin() {
   printf "\b${sp:sc++:1}"
   ((sc==${#sp})) && sc=0
}
endspin() {
   printf "\r%s\n" "$@"
}

until work_done; do
   spin
   some_work ...
done
endspin

15
Гораздо более короткая версия, полностью переносимая *: while :;do for s in / - \\ \|; do printf "\r$s";sleep .1;done;done(*: sleepможет потребоваться целые числа, а не десятичные дроби)
Адам Кац

1
@Daenyth. Спасибо. Пожалуйста, где мы должны вызвать команду, которую нам нужно посмотреть, это прогресс с использованием предыдущего кода?
Горо

@goro: в some_work ...строке выше; более подробное обсуждение, основанное на этом полезном ответе и полезном комментарии Адама Каца - с акцентом на соответствие POSIX - можно найти здесь .
mklement0

@AdamKatz: Это полезное, переносимое упрощение, но для того, чтобы соответствовать подходу Денита, спиннер должен быть основан на, \bа не \r, поскольку в противном случае он будет работать только в самом начале строки: while :; do for c in / - \\ \|; do printf '%s\b' "$c"; sleep 1; done; done- или, если отображать курсор за спиннером нежелательно:printf ' ' && while :; do for c in / - \\ \|; do printf '\b%s' "$c"; sleep 1; done; done
mklement0

1
@kaushal - Ctrl + C остановит его вручную. Если у вас фоновая работа, вы можете сохранить ее PID ( job=$!) и затем запустить while kill -0 $job 2>/dev/null;do …, например:sleep 15 & job=$!; while kill -0 $job 2>/dev/null; do for s in / - \\ \|; do printf "\r$s"; sleep .1; done; done
Адам Кац

48

Некоторые сообщения показали, как отображать прогресс команды. Чтобы рассчитать это, вам нужно увидеть, насколько вы продвинулись. В системах BSD некоторые команды, такие как dd (1), принимают SIGINFOсигнал и сообщают о своем прогрессе. В системах Linux некоторые команды будут реагировать аналогично SIGUSR1. Если эта возможность доступна, вы можете направить свой ввод ddдля контроля количества обработанных байтов.

Кроме того, вы можете использовать, lsofчтобы получить смещение указателя чтения файла и, таким образом, рассчитать прогресс. Я написал команду с именем pmonitor , которая отображает ход обработки указанного процесса или файла. С его помощью вы можете делать такие вещи, как следующее.

$ pmonitor -c gzip
/home/dds/data/mysql-2015-04-01.sql.gz 58.06%

В моем блоге появилась более ранняя версия сценариев оболочки Linux и FreeBSD .


Это потрясающе, я всегда забываю передавать данные через pv :-) Я думаю, что моя команда "stat" работает немного по-другому, моя (Linux) версия этого скрипта: gist.github.com/unhammer/b0ab6a6aa8e1eeaf236b
unhammer

Отличный пост, всегда в восторге от awkигры!
ShellFish

Это замечательно! Спасибо за отличный сценарий!
thebeagle

47

Получил простую функцию индикатора выполнения, которую я написал на днях:

#!/bin/bash
# 1. Create ProgressBar function
# 1.1 Input is currentState($1) and totalState($2)
function ProgressBar {
# Process data
    let _progress=(${1}*100/${2}*100)/100
    let _done=(${_progress}*4)/10
    let _left=40-$_done
# Build progressbar string lengths
    _fill=$(printf "%${_done}s")
    _empty=$(printf "%${_left}s")

# 1.2 Build progressbar strings and print the ProgressBar line
# 1.2.1 Output example:                           
# 1.2.1.1 Progress : [########################################] 100%
printf "\rProgress : [${_fill// /#}${_empty// /-}] ${_progress}%%"

}

# Variables
_start=1

# This accounts as the "totalState" variable for the ProgressBar function
_end=100

# Proof of concept
for number in $(seq ${_start} ${_end})
do
    sleep 0.1
    ProgressBar ${number} ${_end}
done
printf '\nFinished!\n'

Или поймать его,
https://github.com/fearside/ProgressBar/


Можете ли вы объяснить строку под 1.2.1.1, пожалуйста? Вы выполняете подстановку sed переменными _fill и _empty? Я смущен.
Чираг

Вместо использования sed, я использую внутреннюю «замену подстроки» bash, так как это простая работа, я предпочитаю использовать внутренние функции bash для такой работы. Код выглядит лучше. :-) Проверьте здесь tldp.org/LDP/abs/html/string-manipulation.html и найдите замену подстроки.
боязнь

и $ {_ fill} назначается как $ {_ done} количество пробелов. Это прекрасно. Отличная работа, человек. Я определенно собираюсь использовать это во всех моих скриптах bash, ха-ха
Chirag

Отличная работа @fearside! Я сделал небольшую настройку, чтобы пропустить, когда _progress не изменился с последнего значения, чтобы улучшить скорость. github.com/enobufs/bash-tools/blob/master/bin/progbar
enobufs

Сладкий. Изменение черты на прямоугольник придает ему более профессиональный вид:printf "\rProgress : [${_fill// /▇}${_empty// / }] ${_progress}%%"
Мехди ЛАМРАНИ

44

используйте команду linux pv:

http://linux.die.net/man/1/pv

он не знает размер, если он находится в середине потока, но он дает скорость и общее количество, и оттуда вы можете выяснить, сколько времени это займет, и получить обратную связь, чтобы вы знали, что он не завис.


32

Я искал что-то более сексуальное, чем выбранный ответ, как и мой собственный сценарий.

предварительный просмотр

progress-bar.sh в действии

Источник

Я положил его на GitHubprogress-bar.sh

progress-bar() {
  local duration=${1}


    already_done() { for ((done=0; done<$elapsed; done++)); do printf "▇"; done }
    remaining() { for ((remain=$elapsed; remain<$duration; remain++)); do printf " "; done }
    percentage() { printf "| %s%%" $(( (($elapsed)*100)/($duration)*100/100 )); }
    clean_line() { printf "\r"; }

  for (( elapsed=1; elapsed<=$duration; elapsed++ )); do
      already_done; remaining; percentage
      sleep 1
      clean_line
  done
  clean_line
}

Применение

 progress-bar 100

1
Я не понимаю, как это интегрируется в какую-то обработку, где длина процесса неизвестна. Как остановить индикатор выполнения, если мой процесс завершился раньше, например, для разархивирования файла.
Январь

Я думаю, что использование должно бытьprogress-bar 100
Jirarium

Действительно привлекательный прогресс. Как это может быть связано с функцией, которая обрабатывает длительные действия на удаленных серверах через ssh? Я имею в виду, как можно измерить время обновления (например) на удаленных серверах?
безликий,

1
@faceless, это не входит в объем этого кода, вы предоставляете время, и оно ведет обратный отсчет
Эдуард Лопес,

1
@Fusion - это символ Юникода (U + 2587 НИЖНЯЯ СЕМЬ ВОСЬМОГО БЛОКА), который должен быть безопасным для современной оболочки. Попробуй свои зависти
Эдуард Лопес,

18

GNU tar имеет полезную опцию, которая дает функциональность простого индикатора выполнения.

(...) Другое доступное действие контрольной точки - «точка» (или «.»). Он указывает tar печатать одну точку в стандартном потоке листинга, например:

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

Тот же эффект может быть получен путем:

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

+1 за самый простой подход! Если вы не видите напечатанных точек, попробуйте, например, уменьшить число --checkpoint=.10. Он также отлично работает при извлечении с tar -xz.
Ноам Манос

13

Более простой метод, который работает в моей системе с использованием утилиты pipeview (pv).

srcdir=$1
outfile=$2


tar -Ocf - $srcdir | pv -i 1 -w 50 -berps `du -bs $srcdir | awk '{print $1}'` | 7za a -si $outfile

13

Ничего подобного не видели, и все пользовательские функции здесь, кажется, фокусируются только на рендеринге, так что ... мое очень простое POSIX-совместимое решение ниже с пошаговыми пояснениями, потому что этот вопрос не тривиален.

TL; DR

Рендеринг индикатора выполнения очень прост. Оценка того, сколько из этого должно сделать, это другой вопрос. Вот как визуализировать (анимировать) индикатор выполнения - вы можете скопировать и вставить этот пример в файл и запустить его:

#!/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} - значения от 1 до 20
  • echo -n - печатать без новой строки в конце
  • echo -e - интерпретировать специальные символы при печати
  • "\r" - возврат каретки, специальный символ для возврата в начало строки

Вы можете заставить его воспроизводить любой контент с любой скоростью, поэтому этот метод очень универсален, например, часто используется для визуализации «взлома» в глупых фильмах, без шуток.

Полный ответ

Основная проблема заключается в том, как определить $iзначение, т. Е. Сколько отображать индикатор выполнения. В приведенном выше примере я просто позволил увеличить его в forцикле, чтобы проиллюстрировать этот принцип, но реальное приложение будет использовать бесконечный цикл и вычислять $iпеременную на каждой итерации. Для осуществления указанного расчета необходимы следующие ингредиенты:

  1. сколько работы предстоит сделать
  2. сколько работы уже сделано

В случае cpнеобходимости нужен размер исходного файла и размер целевого файла:

#!/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
  • stat - проверить статистику файла
  • -c - вернуть отформатированное значение
  • %s - общий размер

В случае таких операций, как распаковка файла, вычисление исходного размера немного сложнее, но все же так же просто, как получить размер несжатого файла:

#!/bin/sh
src_size=$(gzip -l "$src" | tail -n1 | tr -s ' ' | cut -d' ' -f3)
  • gzip -l - показать информацию о zip-архиве
  • tail -n1 - работа с 1 строкой снизу
  • tr -s ' ' - перевести несколько пробелов в одно (сжать их)
  • cut -d' ' -f3 - вырезать 3-ю колонку, разделенную пробелом

Вот суть проблемы, хотя. Это решение все менее и менее общее. Все расчеты фактического прогресса тесно связаны с доменом, который вы пытаетесь визуализировать, является ли это одной файловой операцией, таймером обратного отсчета, увеличением числа файлов в каталоге, операцией с несколькими файлами и т. Д., Поэтому не может быть повторно использован. Единственная повторно используемая часть - это рендеринг индикатора выполнения. Чтобы использовать его повторно, необходимо абстрагировать его и сохранить в файле (например /usr/lib/progress_bar.sh), а затем определить функции, которые рассчитывают входные значения, специфичные для вашего домена. Вот как может выглядеть обобщенный код (я также сделал $BARдинамику, потому что люди просили об этом, остальное должно быть уже ясно):

#!/bin/sh

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 - встроенный модуль для печати материалов в заданном формате
  • printf '%50s' - ничего не печатать, добавить 50 пробелов
  • tr ' ' '#' - перевести все пробелы в хэш

И вот как вы бы это использовали:

#!/bin/sh

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

Очевидно, что его можно обернуть в функцию, переписать для работы с потоковыми каналами, переписать на другой язык, какой бы ни был ваш яд.


1
Для тех, кому нужны самые простые вещи, я только что сделал свой с cprn first answer. Это очень простой индикатор выполнения в функции, которая использует какое-то глупое правило пропорциональности для рисования индикатора: pastebin.com/9imhRLYX
YCN-


9

Индикатор выполнения в стиле APT (не нарушает нормальный вывод)

введите описание изображения здесь

РЕДАКТИРОВАТЬ: Для получения обновленной версии проверьте мою страницу GitHub

Я не был удовлетворен ответами на этот вопрос. То, что я лично искал, было модным индикатором прогресса, как видит APT.

Я взглянул на исходный код C для APT и решил написать собственный эквивалент для bash.

Этот индикатор будет оставаться в нижней части терминала и не будет мешать выводу на терминал.

Обратите внимание, что в настоящее время панель имеет ширину 100 символов. Если вы хотите масштабировать его до размера терминала, это также довольно легко сделать (обновленная версия на моей странице github хорошо справляется с этим).

Я опубликую свой сценарий здесь. Пример использования:

source ./progress_bar.sh
echo "This is some output"
setup_scroll_area
sleep 1
echo "This is some output 2"
draw_progress_bar 10
sleep 1
echo "This is some output 3"
draw_progress_bar 50
sleep 1
echo "This is some output 4"
draw_progress_bar 90
sleep 1
echo "This is some output 5"
destroy_scroll_area

Скрипт (я настоятельно рекомендую вместо этого версию на моем github):

#!/bin/bash

# This code was inspired by the open source C code of the APT progress bar
# http://bazaar.launchpad.net/~ubuntu-branches/ubuntu/trusty/apt/trusty/view/head:/apt-pkg/install-progress.cc#L233

#
# Usage:
# Source this script
# 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
    # Scroll down a bit to avoid visual glitch when the screen area shrinks by one row
    echo -en "\n"

    # Save cursor
    echo -en "$CODE_SAVE_CURSOR"
    # Set scroll region (this will place the cursor in the top left)
    echo -en "\033[0;${lines}r"

    # Restore cursor but ensure its inside the scrolling area
    echo -en "$CODE_RESTORE_CURSOR"
    echo -en "$CODE_CURSOR_IN_SCROLL_AREA"

    # Start empty progress bar
    draw_progress_bar 0
}

function destroy_scroll_area() {
    lines=$(tput lines)
    # Save cursor
    echo -en "$CODE_SAVE_CURSOR"
    # Set scroll region (this will place the cursor in the top left)
    echo -en "\033[0;${lines}r"

    # Restore cursor but ensure its inside the scrolling area
    echo -en "$CODE_RESTORE_CURSOR"
    echo -en "$CODE_CURSOR_IN_SCROLL_AREA"

    # We are done so clear the scroll bar
    clear_progress_bar

    # Scroll down a bit to avoid visual glitch when the screen area grows by one row
    echo -en "\n\n"
}

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

    # Move cursor position to last row
    echo -en "\033[${lines};0f"

    # Clear progress bar
    tput el

    # Draw progress bar
    print_bar_text $percentage

    # Restore cursor position
    echo -en "$CODE_RESTORE_CURSOR"
}

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

    # Move cursor position to last row
    echo -en "\033[${lines};0f"

    # clear progress bar
    tput el

    # Restore cursor position
    echo -en "$CODE_RESTORE_CURSOR"
}

function print_bar_text() {
    local percentage=$1

    # Prepare progress bar
    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 "]");

    # Print progress bar
    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}"
}

7

Это позволяет вам визуализировать, что команда все еще выполняется:

while :;do echo -n .;sleep 1;done &
trap "kill $!" EXIT  #Die with parent if we die prematurely
tar zxf packages.tar.gz; # or any other command here
kill $! && trap " " EXIT #Kill the loop and unset the trap or else the pid might get reassigned and we might end up killing a completely different process

Это создаст бесконечный цикл while, который выполняется в фоновом режиме и отображает «.» каждую секунду. Это будет отображаться .в оболочке. Запустите tarкоманду или любую команду, которую вы хотите. Когда эта команда завершается исполняющие затем убить последнее задание работает в фоновом режиме - который является бесконечным во время цикла .


Не может ли другое задание начаться в фоновом режиме во время выполнения и потенциально может быть уничтожено вместо цикла выполнения?
Сентиман

Я думаю, что идея заключается в том, чтобы вы поместили это в сценарий, так что это только приведет к выходу этого сценария.
Iguananaut

1
Мне нравится эта команда, я использую ее в своих файлах. Мне просто немного неловко, так как я не совсем понимаю, как это работает. Первые и третьи строки легче понять, но я все еще не уверен. Я знаю, что это старый ответ, но есть ли способ получить другое объяснение, ориентированное на новичков при программировании
Фелипе

1
Это ЕДИНСТВЕННЫЙ верный ответ, когда другие представляют собой просто скриптовые индикаторы хода выполнения 101, которые ничего не значат и бесполезны для реальных, одноразовых, практически не отслеживаемых (почти ВСЕХ) программ. Спасибо.
января

@Felipe, цикл while - это фоновый процесс. $! в первой ловушке захватывает идентификатор процесса этого фонового процесса и гарантирует, что, если текущий / родительский процесс завершается, фоновый процесс тоже умирает, и он не остается зависшим. Оператор kill просто завершает фоновый процесс, когда ваша длинная команда или команды заканчиваются.
Флойд

7

Вот как это может выглядеть

Загрузка файла

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

Ожидание завершения работы

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

Простая функция, которая реализует это

Вы можете просто скопировать и вставить его в свой скрипт. Больше ничего не требуется для работы.

PROGRESS_BAR_WIDTH=50  # progress bar length in characters

draw_progress_bar() {
  # Arguments: current value, max value, unit of measurement (optional)
  local __value=$1
  local __max=$2
  local __unit=${3:-""}  # if unit is not supplied, do not display it

  # Calculate percentage
  if (( $__max < 1 )); then __max=1; fi  # anti zero division protection
  local __percentage=$(( 100 - ($__max*100 - $__value*100) / $__max ))

  # Rescale the bar according to the progress bar width
  local __num_bar=$(( $__percentage * $PROGRESS_BAR_WIDTH / 100 ))

  # Draw progress bar
  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"
}

Пример использования

Здесь мы загружаем файл и перерисовываем индикатор выполнения на каждой итерации. Неважно, какое задание фактически выполняется, пока мы можем получить 2 значения: максимальное значение и текущее значение.

В приведенном ниже примере максимальное значение равно, file_sizeа текущее значение предоставляется некоторой функцией и вызывается uploaded_bytes.

# Uploading a file
file_size=137921

while true; do
  # Get current value of uploaded bytes
  uploaded_bytes=$(some_function_that_reports_progress)

  # Draw a progress bar
  draw_progress_bar $uploaded_bytes $file_size "bytes"

  # Check if we reached 100%
  if [ $uploaded_bytes == $file_size ]; then break; fi
  sleep 1  # Wait before redrawing
done
# Go to the newline at the end of upload
printf "\n"

Аккуратная и простая функция. Большое спасибо!
Андреас Крафт

Это то, что я ищу! Большое спасибо :)
wajdi_jurry

4

Большинство команд Unix не даст вам прямой обратной связи, с которой вы можете сделать это. Некоторые выдадут вам вывод на stdout или stderr, который вы можете использовать.

Для чего-то вроде tar вы можете использовать ключ -v и направить вывод в программу, которая обновляет небольшую анимацию для каждой строки, которую она читает. Когда tar записывает список файлов, которые он распаковывает, программа может обновить анимацию. Чтобы выполнить процент выполнения, вам нужно знать количество файлов и считать строки.

Насколько я знаю, cp не дает такого рода вывода. Чтобы отслеживать прогресс cp, вам нужно будет отслеживать исходные и конечные файлы и следить за размером получателя. Вы можете написать небольшую программу на c, используя системный вызов stat (2), чтобы получить размер файла. Это будет считывать размер источника, а затем опрашивать файл назначения и обновлять панель% complete в зависимости от размера файла, записанного на сегодняшний день.


4

Мое решение отображает процентную долю архива, который в данный момент распаковывается и записывается. Я использую это, когда записываю образы корневой файловой системы 2 ГБ. Вам действительно нужен индикатор прогресса для этих вещей. Что я делаю, это использую, gzip --listчтобы получить полный несжатый размер тарбола. Исходя из этого, я вычисляю фактор блокировки, необходимый для разделения файла на 100 частей. Наконец, я печатаю сообщение контрольной точки для каждого блока. Для файла объемом 2 ГБ это дает около 10 МБ блока. Если он слишком велик, вы можете разделить BLOCKING_FACTOR на 10 или 100, но тогда будет сложнее вывести симпатичный результат в процентах.

Предполагая, что вы используете Bash, вы можете использовать следующую функцию оболочки

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

Хорошее решение, но как быть, когда вы хотите сжать каталог?
Самир Садек

4

Прежде всего, бар - это не единственный индикатор хода трубы. Другой (может быть, даже более известный) - pv (pipe viewer).

Во-вторых, bar и pv можно использовать, например, так:

$ bar file1 | wc -l 
$ pv file1 | wc -l

или даже:

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

Один полезный трюк, если вы хотите использовать bar и pv в командах, которые работают с файлами, указанными в аргументах, таких как, например, copy file1 file2, - это использовать подстановку процесса :

$ copy <(bar file1) file2
$ copy <(pv file1) file2

Подстановка процессов - это волшебство bash, которое создает временные файлы fifo pipe / dev / fd / и подключает стандартный вывод из запущенного процесса (в круглых скобках) через этот канал, и копия видит его как обычный файл (за одним исключением, он может только читать его). вперед).

Обновить:

Сама команда bar также позволяет копировать. После бара человек:

bar --in-file /dev/rmt/1cbn --out-file \
     tape-restore.tar --size 2.4g --buffer-size 64k

Но процесс замены, по моему мнению, является более общим способом сделать это. Он использует саму программу cp.


3

Я предпочитаю использовать диалог с параметром --gauge . Очень часто используется в установочных пакетах .deb и других элементах базовой конфигурации многих дистрибутивов. Таким образом, вам не нужно изобретать велосипед ... снова

Просто введите значение от 1 до 100 @stdin. Один простой и глупый пример:

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

У меня есть этот файл / bin / Wait (с chmod u + x perms) для приготовления пищи: 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 seconds left ($MLEFT minutes)";
    TITLE="Waiting $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="Espera terminada: $1";audio="Listo";
else msg=$2;audio=$2;fi 

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

Так что я могу поставить:

Wait "34 min" "warm up the oven"

или

Wait "dec 31" "happy new year"


2

для меня самый простой в использовании и самый красивый вид пока это команда pvили barкакой-то парень уже написал

например: необходимо сделать резервную копию всего диска с dd

обычно вы используете dd if="$input_drive_path" of="$output_file_path"

с этим pvвы можете сделать это так:

dd if="$input_drive_path" | pv | dd of="$output_file_path"

и прогресс идет прямо к STDOUTэтому:

    7.46GB 0:33:40 [3.78MB/s] [  <=>                                            ]

после того, как это сделано, сводится

    15654912+0 records in
    15654912+0 records out
    8015314944 bytes (8.0 GB) copied, 2020.49 s, 4.0 MB/s

Можете ли вы использовать pvили barдля визуализации хода выполнения различных процессов, например, таймера обратного отсчета, положения в текстовом файле, установки вашего приложения, настройки времени выполнения и т. Д.?
cprn

2

Многие ответы описывают написание ваших собственных команд для распечатки '\r' + $some_sort_of_progress_msg. Иногда проблема заключается в том, что распечатка сотен этих обновлений в секунду замедляет процесс.

Однако, если какой-либо из ваших процессов выдает результат (например 7z a -r newZipFile myFolder, выводит каждое имя файла по мере его сжатия), тогда существует более простое, быстрое, безболезненное и настраиваемое решение.

Установите модуль Python tqdm.

$ sudo pip install tqdm
$ # now have fun
$ 7z a -r -bd newZipFile myFolder | tqdm >> /dev/null
$ # if we know the expected total, we can have a bar!
$ 7z a -r -bd newZipFile myFolder | grep -o Compressing | tqdm --total $(find myFolder -type f | wc -l) >> /dev/null

Помощь: tqdm -h. Пример использования дополнительных параметров:

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

В качестве бонуса вы также можете использовать, tqdmчтобы обернуть итерируемые в код Python.

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


Я не думаю, что ваш пример с «больше вариантов» работает. Кажется, чтобы передать tqdmSTDOUT wc -lчерез трубу. Вы, вероятно, хотите избежать этого.
cprn

1
@cprn tqdmбудет показывать прогресс в STDERRто время как по конвейеру своего вклада STDINв STDOUT. В этом случае wc -lдолжен получить тот же вход, как если бы tqdmон не был включен.
casper.dcl

Ах, теперь имеет смысл. Спасибо за объяснение.
cprn

2

Основываясь на работе Эдуарда Лопеса, я создал индикатор выполнения, который соответствует размеру экрана, каким бы он ни был. Проверьте это.

введите описание изображения здесь

Он также размещен на Git Hub .

#!/bin/bash
#
# Progress bar by Adriano Pinaffo
# Available at https://github.com/adriano-pinaffo/progressbar.sh
# Inspired on work by Edouard Lopez (https://github.com/edouard-lopez/progress-bar.sh)
# Version 1.0
# Date April, 28th 2017

function error {
  echo "Usage: $0 [SECONDS]"
  case $1 in
    1) echo "Pass one argument only"
    exit 1
    ;;
    2) echo "Parameter must be a number"
    exit 2
    ;;
    *) echo "Unknown error"
    exit 999
  esac
}

[[ $# -ne 1 ]] && error 1
[[ $1 =~ ^[0-9]+$ ]] || error 2

duration=${1}
barsize=$((`tput cols` - 7))
unity=$(($barsize / $duration))
increment=$(($barsize%$duration))
skip=$(($duration/($duration-$increment)))
curr_bar=0
prev_bar=
for (( elapsed=1; elapsed<=$duration; elapsed++ ))
do
  # Elapsed
prev_bar=$curr_bar
  let curr_bar+=$unity
  [[ $increment -eq 0 ]] || {  
    [[ $skip -eq 1 ]] &&
      { [[ $(($elapsed%($duration/$increment))) -eq 0 ]] && let curr_bar++; } ||
    { [[ $(($elapsed%$skip)) -ne 0 ]] && let curr_bar++; }
  }
  [[ $elapsed -eq 1 && $increment -eq 1 && $skip -ne 1 ]] && let curr_bar++
  [[ $(($barsize-$curr_bar)) -eq 1 ]] && let curr_bar++
  [[ $curr_bar -lt $barsize ]] || curr_bar=$barsize
  for (( filled=0; filled<=$curr_bar; filled++ )); do
    printf "▇"
  done

  # Remaining
  for (( remain=$curr_bar; remain<$barsize; remain++ )); do
    printf " "
  done

  # Percentage
  printf "| %s%%" $(( ($elapsed*100)/$duration))

  # Return
  sleep 1
  printf "\r"
done
printf "\n"
exit 0

наслаждаться



1

Это применимо только с использованием gnome zenity. Zenity предоставляет отличный нативный интерфейс для скриптов bash: https://help.gnome.org/users/zenity/stable/

Пример с индикатора прогресса Zenity:

#!/bin/sh
(
echo "10" ; sleep 1
echo "# Updating mail logs" ; sleep 1
echo "20" ; sleep 1
echo "# Resetting cron jobs" ; sleep 1
echo "50" ; sleep 1
echo "This line will just be ignored" ; sleep 1
echo "75" ; sleep 1
echo "# Rebooting system" ; sleep 1
echo "100" ; sleep 1
) |
zenity --progress \
  --title="Update System Logs" \
  --text="Scanning mail logs..." \
  --percentage=0

if [ "$?" = -1 ] ; then
        zenity --error \
          --text="Update canceled."
fi

1

Я использовал ответ из Создание строки повторяющихся символов в сценарии оболочки для повторения символов. У меня есть две относительно небольшие версии bash для сценариев, которые должны отображать индикатор выполнения (например, цикл, который проходит через много файлов, но не полезен для больших файлов tar или операций копирования). Более быстрый состоит из двух функций, одна из которых предназначена для подготовки строк для отображения панели:

preparebar() {
# $1 - bar length
# $2 - bar char
    barlen=$1
    barspaces=$(printf "%*s" "$1")
    barchars=$(printf "%*s" "$1" | tr ' ' "$2")
}

и один для отображения индикатора выполнения:

progressbar() {
# $1 - number (-1 for clearing the bar)
# $2 - max number
    if [ $1 -eq -1 ]; then
        printf "\r  $barspaces\r"
    else
        barch=$(($1*barlen/$2))
        barsp=$((barlen-barch))
        printf "\r[%.${barch}s%.${barsp}s]\r" "$barchars" "$barspaces"
    fi
}

Это может быть использовано как:

preparebar 50 "#"

что означает подготовить строки для бара с 50 символами "#", и после этого:

progressbar 35 80

отобразит количество символов «#», соответствующее соотношению 35/80:

[#####################                             ]

Имейте в виду, что функция отображает строку в одной и той же строке снова и снова, пока вы (или какая-либо другая программа) не напечатаете новую строку. Если вы установите -1 в качестве первого параметра, строка будет стерта:

progressbar -1 80

Более медленная версия все в одной функции:

progressbar() {
# $1 - number
# $2 - max number
# $3 - number of '#' characters
    if [ $1 -eq -1 ]; then
        printf "\r  %*s\r" "$3"
    else
        i=$(($1*$3/$2))
        j=$(($3-i))
        printf "\r[%*s" "$i" | tr ' ' '#'
        printf "%*s]\r" "$j"
    fi
}

и его можно использовать как (тот же пример, что и выше):

progressbar 35 80 50

Если вам нужен прогрессбар на stderr, просто добавьте >&2в конце каждой команды printf.


1

Чтобы указать прогресс деятельности, попробуйте следующие команды:

while true; do sleep 0.25 && echo -ne "\r\\" && sleep 0.25 && echo -ne "\r|" && sleep 0.25 && echo -ne "\r/" && sleep 0.25 && echo -ne "\r-"; done;

ИЛИ

while true; do sleep 0.25 && echo -ne "\rActivity: \\" && sleep 0.25 && echo -ne "\rActivity: |" && sleep 0.25 && echo -ne "\rActivity: /" && sleep 0.25 && echo -ne "\rActivity: -"; done;

ИЛИ

while true; do sleep 0.25 && echo -ne "\r" && sleep 0.25 && echo -ne "\r>" && sleep 0.25 && echo -ne "\r>>" && sleep 0.25 && echo -ne "\r>>>"; sleep 0.25 && echo -ne "\r>>>>"; done;

ИЛИ

while true; do sleep .25 && echo -ne "\r:Active:" && sleep .25 && echo -ne "\r:aCtive:" && sleep .25 && echo -ne "\r:acTive:" && sleep .25 && echo -ne "\r:actIve:" && sleep .25 && echo -ne "\r:actiVe:" && sleep .25 && echo -ne "\r:activE:"; done;

Внутри цикла while можно использовать флаги / переменные для проверки и отображения значения / степени прогресса.


1

Используя предложения, перечисленные выше, я решил реализовать свой собственный индикатор выполнения.

#!/usr/bin/env bash

main() {
  for (( i = 0; i <= 100; i=$i + 1)); do
    progress_bar "$i"
    sleep 0.1;
  done
  progress_bar "done"
  exit 0
}

progress_bar() {
  if [ "$1" == "done" ]; then
    spinner="X"
    percent_done="100"
    progress_message="Done!"
    new_line="\n"
  else
    spinner='/-\|'
    percent_done="${1:-0}"
    progress_message="$percent_done %"
  fi

  percent_none="$(( 100 - $percent_done ))"
  [ "$percent_done" -gt 0 ] && local done_bar="$(printf '#%.0s' $(seq -s ' ' 1 $percent_done))"
  [ "$percent_none" -gt 0 ] && local none_bar="$(printf '~%.0s' $(seq -s ' ' 1 $percent_none))"

  # print the progress bar to the screen
  printf "\r Progress: [%s%s] %s %s${new_line}" \
    "$done_bar" \
    "$none_bar" \
    "${spinner:x++%${#spinner}:1}" \
    "$progress_message"
}

main "$@"

1
Ницца! чтобы заставить его работать, я должен был изменить строку percent_none="$(( 100 - "$percent_done" ))"наpercent_none="$(( 100 - $percent_done))"
sergio

0

Я сделал чистую версию оболочки для встроенной системы, используя преимущества:

  • Функция обработки сигналов в / usr / bin / dd SIGUSR1.

    По сути, если вы отправите 'kill SIGUSR1 $ (pid_of_running_dd_process)', он выдаст сводную информацию о скорости передачи и количестве переданных.

  • создание фонового dd, регулярный запрос его обновлений и генерация тиков хеша, как это делали старые ftp-клиенты.

  • Использование / dev / stdout в качестве места назначения для не дружественных для stdout программ, таких как scp

Конечный результат позволяет вам выполнить любую операцию по передаче файла и получить обновление прогресса, которое выглядит как вывод старой хеш-функции с FTP, где вы просто получите хеш-метку для каждого X байтов.

Это вряд ли код качества производства, но вы поняли идею. Я думаю, что это мило.

Что бы это ни стоило, фактическое количество байтов может не отражаться правильно в количестве хэшей - у вас может быть один больше или меньше в зависимости от проблем округления. Не используйте это как часть тестового сценария, это просто конфетка. И да, я знаю, что это ужасно неэффективно - это сценарий оболочки, и я не извиняюсь за это.

Примеры с wget, scp и tftp приведены в конце. Он должен работать со всем, что имеет данные. Убедитесь, что вы используете / dev / stdout для программ, которые не подходят для stdout.

#!/bin/sh
#
# Copyright (C) Nathan Ramella (nar+progress-script@remix.net) 2010 
# LGPLv2 license
# If you use this, send me an email to say thanks and let me know what your product
# is so I can tell all my friends I'm a big man on the internet!

progress_filter() {

        local START=$(date +"%s")
        local SIZE=1
        local DURATION=1
        local BLKSZ=51200
        local TMPFILE=/tmp/tmpfile
        local PROGRESS=/tmp/tftp.progress
        local BYTES_LAST_CYCLE=0
        local BYTES_THIS_CYCLE=0

        rm -f ${PROGRESS}

        dd bs=$BLKSZ of=${TMPFILE} 2>&1 \
                | grep --line-buffered -E '[[:digit:]]* bytes' \
                | awk '{ print $1 }' >> ${PROGRESS} &

        # Loop while the 'dd' exists. It would be 'more better' if we
        # actually looked for the specific child ID of the running 
        # process by identifying which child process it was. If someone
        # else is running dd, it will mess things up.

        # My PID handling is dumb, it assumes you only have one running dd on
        # the system, this should be fixed to just get the PID of the child
        # process from the shell.

        while [ $(pidof dd) -gt 1 ]; do

                # PROTIP: You can sleep partial seconds (at least on linux)
                sleep .5    

                # Force dd to update us on it's progress (which gets
                # redirected to $PROGRESS file.
                # 
                # dumb pid handling again
                pkill -USR1 dd

                local BYTES_THIS_CYCLE=$(tail -1 $PROGRESS)
                local XFER_BLKS=$(((BYTES_THIS_CYCLE-BYTES_LAST_CYCLE)/BLKSZ))

                # Don't print anything unless we've got 1 block or more.
                # This allows for stdin/stderr interactions to occur
                # without printing a hash erroneously.

                # Also makes it possible for you to background 'scp',
                # but still use the /dev/stdout trick _even_ if scp
                # (inevitably) asks for a password. 
                #
                # Fancy!

                if [ $XFER_BLKS -gt 0 ]; then
                        printf "#%0.s" $(seq 0 $XFER_BLKS)
                        BYTES_LAST_CYCLE=$BYTES_THIS_CYCLE
                fi
        done

        local SIZE=$(stat -c"%s" $TMPFILE)
        local NOW=$(date +"%s")

        if [ $NOW -eq 0 ]; then
                NOW=1
        fi

        local DURATION=$(($NOW-$START))
        local BYTES_PER_SECOND=$(( SIZE / DURATION ))
        local KBPS=$((SIZE/DURATION/1024))
        local MD5=$(md5sum $TMPFILE | awk '{ print $1 }')

        # This function prints out ugly stuff suitable for eval() 
        # rather than a pretty string. This makes it a bit more 
        # flexible if you have a custom format (or dare I say, locale?)

        printf "\nDURATION=%d\nBYTES=%d\nKBPS=%f\nMD5=%s\n" \
            $DURATION \
            $SIZE \
            $KBPS \
            $MD5
}

Примеры:

echo "wget"
wget -q -O /dev/stdout http://www.blah.com/somefile.zip | progress_filter

echo "tftp"
tftp -l /dev/stdout -g -r something/firmware.bin 192.168.1.1 | progress_filter

echo "scp"
scp user@192.168.1.1:~/myfile.tar /dev/stdout | progress_filter

Достойная идея, если у вас есть заранее заданный размер файла, вы можете обеспечить дополнительную ценность, чем pv, но слепо сигнализировать об pidof ddэтом страшно.

Попытка вызвать это с помощью «# Моя обработка PID глупа»
synthesizerpatel

Вы , возможно , можете захватить $!с ddи ждать [[ -e /proc/${DD_PID} ]].

0

Если вам нужно показать временную шкалу прогресса (заранее зная время показа), вы можете использовать Python следующим образом:

#!/bin/python
from time import sleep
import sys

if len(sys.argv) != 3:
    print "Usage:", sys.argv[0], "<total_time>", "<progressbar_size>"
    exit()

TOTTIME=float(sys.argv[1])
BARSIZE=float(sys.argv[2])

PERCRATE=100.0/TOTTIME
BARRATE=BARSIZE/TOTTIME

for i in range(int(TOTTIME)+1):
    sys.stdout.write('\r')
    s = "[%-"+str(int(BARSIZE))+"s] %d%% "
    sys.stdout.write(s % ('='*int(BARRATE*i), int(PERCRATE*i)))
    sys.stdout.flush()
    SLEEPTIME = 1.0
    if i == int(TOTTIME): SLEEPTIME = 0.1
    sleep(SLEEPTIME)
print ""

Затем, предполагая, что вы сохранили скрипт Python как progressbar.py, можно показать индикатор выполнения из вашего скрипта bash, выполнив следующую команду:

python progressbar.py 10 50

Он будет отображать индикатор выполнения размером с 50символы и «работает» в течение 10нескольких секунд.


0

Я основывался на ответе, представленном страхом

Это подключается к базе данных Oracle для получения информации о ходе восстановления RMAN.

#!/bin/bash

 # 1. Create ProgressBar function
 # 1.1 Input is currentState($1) and totalState($2)
 function ProgressBar {
 # Process data
let _progress=(${1}*100/${2}*100)/100
let _done=(${_progress}*4)/10
let _left=40-$_done
# Build progressbar string lengths
_fill=$(printf "%${_done}s")
_empty=$(printf "%${_left}s")

# 1.2 Build progressbar strings and print the ProgressBar line
# 1.2.1 Output example:
# 1.2.1.1 Progress : [########################################] 100%
printf "\rProgress : [${_fill// /#}${_empty// /-}] ${_progress}%%"

}

function rman_check {
sqlplus -s / as sysdba <<EOF
set heading off
set feedback off
select
round((sofar/totalwork) * 100,0) pct_done
from
v\$session_longops
where
totalwork > sofar
AND
opname NOT LIKE '%aggregate%'
AND
opname like 'RMAN%';
exit
EOF
}

# Variables
_start=1

# This accounts as the "totalState" variable for the ProgressBar function
_end=100

_rman_progress=$(rman_check)
#echo ${_rman_progress}

# Proof of concept
#for number in $(seq ${_start} ${_end})

while [ ${_rman_progress} -lt 100 ]
do

for number in _rman_progress
do
sleep 10
ProgressBar ${number} ${_end}
done

_rman_progress=$(rman_check)

done
printf '\nFinished!\n'

0
#!/bin/bash

function progress_bar() {
    bar=""
    total=10
    [[ -z $1 ]] && input=0 || input=${1}
    x="##"
   for i in `seq 1 10`; do
        if [ $i -le $input ] ;then
            bar=$bar$x
        else
            bar="$bar  "
       fi
    done
    #pct=$((200*$input/$total % 2 + 100*$input/$total))
    pct=$(($input*10))
    echo -ne "Progress : [ ${bar} ] (${pct}%) \r"    
    sleep 1
    if [ $input -eq 10 ] ;then
        echo -ne '\n'
    fi

}

Можно создать функцию, которая рисует это в масштабе, скажем, 1-10 для количества баров:

progress_bar 1
echo "doing something ..."
progress_bar 2
echo "doing something ..."
progress_bar 3
echo "doing something ..."
progress_bar 8
echo "doing something ..."
progress_bar 10

0
#!/bin/bash
tot=$(wc -c /proc/$$/fd/255 | awk '/ /{print $1}')
now() {
echo $(( 100* ($(awk '/^pos:/{print $2}' < /proc/$$/fdinfo/255)-166) / (tot-166) )) "%"
}
now;
now;
now;
now;
now;
now;
now;
now;
now;

вывод:

0 %
12 %
25 %
37 %
50 %
62 %
75 %
87 %
100 %

примечание: если вместо 255 вы введете 1, вы будете контролировать стандарт на ... с 2 на стандарт на выходе (но вы должны изменить источник, чтобы установить "tot" в соответствии с прогнозируемым размером выходного файла)

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.