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

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

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

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

Ответ 1

Вы можете реализовать это, перезаписав строку. Используйте \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 упоминает, что это "не удается", если вы начинаете с длинной строки, а затем хотите написать короткую строку: в этом случае вам нужно будет переписать длину длинной строки (например, пробелами).

Ответ 2

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

Можно ли сделать счетчик в Bash?

Конечно!

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

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

Строка\b заменяется символом "backspace". С другой стороны, вы можете играть с \r, чтобы вернуться к началу строки.

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

эквивалент 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" "[email protected]"
}

until work_done; do
   spin
   some_work ...
done
endspin

Ответ 3

В некоторых сообщениях показано, как отобразить ход команды. Чтобы рассчитать его, вам нужно посмотреть, сколько вы продвинулись. В 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 появляется на в моем блоге.

Ответ 4

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

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

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

Ответ 5

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

#!/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/

Ответ 6

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

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

progress-bar.sh in action

Источник

Я положил его на github progress-bar.sh -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

Ответ 7

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

(...) Еще одно доступное действие контрольной точки - "точка (или".). Он инструктирует tar печатать одну точку в стандартном потоке листинга, например:

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

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

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

Ответ 8

Более простой метод, который работает в моей системе с помощью утилиты 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

Ответ 10

Ничего подобного не видел, так что... мое очень простое 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 work there is to be done

while true; do
    tgt_size=$(stat -c%s "$tgt")       # how much work is 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;
    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 work there is to do

while true; do
    work_done=$(get_work_done) # how much work 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

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

Ответ 11

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

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 или любую команду, которую вы хотите. Когда эта команда завершает выполнение, тогда убить последнее задание, выполняемое в фоновом режиме - это бесконечный цикл while.

Ответ 12

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

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

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

Ответ 13

В моем решении отображается процентный размер tarball, который в настоящее время несжатый и написанный. Я использую это при записи 2GB файлов корневой файловой системы. Вы действительно нужен индикатор прогресса для этих вещей. Я использую 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}
}

Ответ 14

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

Второй бар и 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/dev/fd/и соединяет stdout из запущенного процесса (внутри скобок) через этот канал, и копия видит его как обычный файл (за одним исключением, он может читать только вперед).

Update:

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

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

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

Ответ 15

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

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

[##################################################] 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"

Ответ 16

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

enter image description here

РЕДАКТИРОВАТЬ: Для получения обновленной версии проверьте мою страницу 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}"
}

Ответ 17

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

Просто поместите значение int от 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"

Ответ 18

для меня проще всего использовать и лучше всего смотреть на команду 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

Ответ 19

Многие ответы описывают создание собственных команд для печати '\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

Ответ 21

Это применимо только с помощью 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

Ответ 22

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

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 можно использовать флаги/переменные, чтобы проверить и отобразить значение/степень прогресса.

Ответ 23

Я использовал ответ из создания строки повторяющихся символов в оболочке script для повторения char. У меня есть две относительно небольшие версии 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.

Ответ 24

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

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

Он также размещен на 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

Enjoy

Ответ 25

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

#!/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 "[email protected]"

Ответ 26

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

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

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

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

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

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

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

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

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

#!/bin/sh
#
# Copyright (C) Nathan Ramella ([email protected]) 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 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 [email protected]:~/myfile.tar /dev/stdout | progress_filter

Ответ 27

Если вам нужно отобразить временной индикатор выполнения (зная заранее время показа), вы можете использовать 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 script как progressbar.py, можно показать индикатор выполнения из bash script, выполнив следующую команду:

python progressbar.py 10 50

Отобразится индикатор выполнения, размер 50 и "работа" за 10 секунды.

Ответ 28

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

Это соединение с базой данных 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'

Ответ 29

#!/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

Ответ 30

#!/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" в соответствии с прогнозируемым размером выходного файла)