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

337

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

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

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

  • 0
    См. Также stackoverflow.com/questions/12498304/… для примеров логики управления (справьтесь с заданием и сделайте что-нибудь, пока оно не закончится).
  • 1
    Существует ряд требований, которые мы часто находим полезными при написании сценариев. ведение журнала, отображение прогресса, цвета, необычные результаты и т. д. Я всегда чувствовал, что должна быть какая-то простая среда сценариев. Наконец, я решил реализовать один, так как я не мог найти ни одного. Вы можете найти это полезным. Это в чистом Bash, я имею в виду Just Bash. github.com/SumuduLansakara/JustBash
Показать ещё 1 комментарий
Теги:
zsh

36 ответов

674
Лучший ответ

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

  • 20
    В соответствии с man-страницей echo (по крайней мере, в MacOS X) sh / bash использует свою собственную встроенную команду echo , которая не принимает «-n» ... так что для достижения того же, вам нужно поставить \ r \ c в конце строки, а не просто \ r
  • 1
    Я думаю, что вы имеете в виду, что версия Mac не принимает -e? Вы правы в том, что -e кажется расширением GNU.
Показать ещё 12 комментариев
60

Вы также можете быть заинтересованы в как сделать 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" "$@"
}

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. Благодарю. Пожалуйста, где мы должны вызвать команду, которую нам нужно посмотреть, это прогресс с использованием предыдущего кода?
Показать ещё 5 комментариев
50

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

  • 0
    Это потрясающе, я всегда забываю передавать данные через pv :-) Я думаю, что моя команда "stat" работает немного по-другому, моя (Linux) версия этого скрипта: gist.github.com/unhammer/b0ab6a6aa8e1eeaf236b
  • 0
    Отличный пост, всегда в awk !
Показать ещё 1 комментарий
37

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

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

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

36

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

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

  • 0
    Можете ли вы объяснить строку под 1.2.1.1, пожалуйста? Вы выполняете замену sed с помощью переменных _fill и _empty? Я не совсем понимаю.
  • 0
    Вместо использования sed, я использую внутреннюю «замену подстроки» bash, так как это простая работа, я предпочитаю использовать внутренние функции bash для такой работы. Код выглядит лучше. :-) Проверьте здесь tldp.org/LDP/abs/html/string-manipulation.html и найдите замену подстроки.
Показать ещё 2 комментария
22

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

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

Изображение 3889

Источник

Я положил его на 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
  • 0
    Я не понимаю, как это интегрируется в какую-то обработку, где длина процесса неизвестна. Как остановить индикатор выполнения, если мой процесс завершился раньше, например, для разархивирования файла.
  • 0
    Я думаю, что использование должно быть progress-bar 100
Показать ещё 3 комментария
17

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

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

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

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

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

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

Я также хотел бы внести свой собственный индикатор прогресса

Он достигает субактериальной точности, используя Half Unicode block

Изображение 3890

Код включен

6

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

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.

  • 0
    Не может ли другое задание начаться в фоновом режиме во время выполнения и потенциально может быть уничтожено вместо цикла выполнения?
  • 0
    Я думаю, что идея заключается в том, чтобы вы поместили это в сценарий, так что это только приведет к выходу этого сценария.
Показать ещё 3 комментария
5

Не видел ничего подобного... мое очень простое решение:

#!/bin/bash
BAR='####################'   # this is full bar, mine is 20 chars
for i in {1..20}; do
    echo -ne "\r${BAR:0:$i}" # print $i chars of $BAR from 0 position
    sleep .1
done
  • echo -n - печать без новой строки в конце
  • echo -e - интерпретировать специальные символы при печати
  • "\r" - возврат каретки, специальный char для возврата к началу строки

Я использовал его давно в простом "взломанном видео", чтобы имитировать код ввода.;)

4

Прежде всего, бар - это не единственный измеритель хода трубы. Другой (возможно, еще более известный) - 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.

4

В моем решении отображается процентный размер 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}
}
  • 0
    Хорошее решение, но как быть, когда вы хотите сжать каталог?
3

Я предпочитаю использовать диалог с параметром -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"

3

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

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

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

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

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
1

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

Изображение 3892

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

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

Изображение 3891

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

1

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

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

Это применимо только с помощью 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
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" в соответствии с прогнозируемым размером выходного файла)

0

https://github.com/extensionsapp/progre.sh

Создайте 40-процентный прогресс: progreSh 40

Изображение 3893

0

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

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

[##################################################] 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"
  • 0
    Аккуратная и простая функция. Большое спасибо!
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

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

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

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

0

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

  • /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
  • 0
    Неплохая идея, если вы заранее определили размер файла, вы можете таким образом обеспечить добавленную стоимость, чем pv, но вслепую сигнализировать о pidof dd страшно.
  • 0
    Попытка вызвать это с помощью «# Моя обработка PID глупа»
Показать ещё 1 комментарий
-1

Сначала запустите процесс в фоновом режиме, а затем посмотрите, как он работает, часто, который запускал печать шаблона и снова проверял, что он запущен или нет;

Использование цикла while для частого просмотра состояния процесса.

используйте pgrep или любую другую команду для просмотра и получения состояния процесса.

если pgrep перенаправляет ненужный вывод на /dev/null при необходимости.

Код:

sleep 12&
while pgrep sleep &> /dev/null;do echo -en "#";sleep 0.5;done

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

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

Ошибка:  этот метод pgrep не работает во всех ситуациях, неожиданно другой процесс работал с тем же именем, цикл while не заканчивается.

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

команда ps a отобразит весь процесс с идентификатором, вам нужно grep, чтобы узнать pid указанного процесса

-1

Это психоделический индикатор прогресса для bash сценариев nExace. Его можно вызвать из командной строки как "./progressbar x y", где "x" - это время в секундах, а "y" - сообщение, связанное с этой частью прогресса.

Внутренняя функция progressbar() сама по себе является хорошей автономной, если вы хотите, чтобы другие части вашего script контролировали панель прогресса. Например, отправка "progressbar 10" Создание дерева каталогов ";" отобразит:

[#######                                     ] (10%) Creating directory tree

Конечно, это будет прекрасно психоделично, хотя...

#!/bin/bash

if [ "$#" -eq 0 ]; then echo "x is \"time in seconds\" and z is \"message\""; echo "Usage: progressbar x z"; exit; fi
progressbar() {
        local loca=$1; local loca2=$2;
        declare -a bgcolors; declare -a fgcolors;
        for i in {40..46} {100..106}; do
                bgcolors+=("$i")
        done
        for i in {30..36} {90..96}; do
                fgcolors+=("$i")
        done
        local u=$(( 50 - loca ));
        local y; local t;
        local z; z=$(printf '%*s' "$u");
        local w=$(( loca * 2 ));
        local bouncer=".oO°Oo.";
        for ((i=0;i<loca;i++)); do
                t="${bouncer:((i%${#bouncer})):1}"
                bgcolor="\\E[${bgcolors[RANDOM % 14]}m \\033[m"
                y+="$bgcolor";
        done
        fgcolor="\\E[${fgcolors[RANDOM % 14]}m"
        echo -ne " $fgcolor$t$y$z$fgcolor$t \\E[96m(\\E[36m$w%\\E[96m)\\E[92m $fgcolor$loca2\\033[m\r"
};
timeprogress() {
        local loca="$1"; local loca2="$2";
        loca=$(bc -l <<< scale=2\;"$loca/50")
        for i in {1..50}; do
                progressbar "$i" "$loca2";
                sleep "$loca";
        done
        printf "\n"
};
timeprogress "$1" "$2"
  • 0
    красиво психоделический это небольшое преуменьшение. Интересный подход. Одно замечание: нет необходимости заставлять интерпретации escape-последовательностей с помощью echo генерировать newline (например, echo -e "\n" ), достаточно простого echo "" .
  • 0
    Спасибо за комментарий. Вы правы насчет эха, однако я стараюсь избегать двойных цитат "" по эстетическим соображениям. Мне нравится "видеть" новую строку (\ n).
Показать ещё 2 комментария
-1

Чтобы сделать индикатор выполнения tar

tar xzvf pippo.tgz |xargs -L 19 |xargs -I@ echo -n "."

Где "19" - количество файлов в tar, делят длину намеченного индикатора выполнения. Пример:.tgz содержит 140 файлов, и вам понадобится индикатор выполнения 76 ".", Вы можете положить -L 2.

Вам больше ничего не понадобится.

  • 0
    Это относится к tar , поэтому, если сценарий оболочки состоит только из команды tar , он на самом деле не применим к случаю OP.
-2

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

#!/bin/bash
function lines {
  local file=$1
  local default=$2
  if [[ -f $file ]]; then
    wc -l $file | awk '{print $1}';
  else
    echo $default
  fi
}

function bar {
  local items=$1
  local total=$2
  local size=$3
  percent=$(($items*$size/$total % $size))
  left=$(($size-$percent))
  chars=$(local s=$(printf "%${percent}s"); echo "${s// /=}")
  echo -ne "[$chars>";
  printf "%${left}s"
  echo -ne ']\r'
}

function clearbar {
  local size=$1
  printf " %${size}s  "
  echo -ne "\r"
}

function progress {
  local pid=$1
  local total=$2
  local file=$3

  bar 0 100 50
  while [[ "$(ps a | awk '{print $1}' | grep $pid)" ]]; do
    bar $(lines $file 0) $total 50
    sleep 1
  done
  clearbar 50
  wait $pid
  return $?
}

Затем используйте его следующим образом:

target=$(lines build.log 1000)
(mvn clean install > build.log 2>&1) &
progress $! $target build.log

Он выводит индикатор выполнения, который выглядит примерно так:

[===============================================>   ]

Полоса растет, когда количество строк достигает цели. Если количество строк превышает цель, панель начинается (надеюсь, цель хороша).

BTW: Я использую bash в Mac OSX. Я основывал этот код на spinner от mariascio.

-4

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

#!/bin/bash
# Updates the progress bar
# Parameters: 1. Percentage value
update_progress_bar()
{
  if [ $# -eq 1 ];
  then
    if [[ $1 == [0-9]* ]];
    then
      if [ $1 -ge 0 ];
      then
        if [ $1 -le 100 ];
        then
          local val=$1
          local max=100

          echo -n "["

          for j in $(seq $max);
          do
            if [ $j -lt $val ];
            then
              echo -n "="
            else
              if [ $j -eq $max ];
              then
                echo -n "]"
              else
                echo -n "."
              fi
            fi
          done

          echo -ne " "$val"%\r"

          if [ $val -eq $max ];
          then
            echo ""
          fi
        fi
      fi
    fi
  fi
}

update_progress_bar 0
# Further (time intensive) actions and progress bar updates
update_progress_bar 100
  • 1
    Вы можете свернуть эти первые четыре if в один оператор if с серией AND, поскольку у вас нет какого-либо конкретного кода внутри них: if [ $# -eq 1 ] && [[ $1 == [0-9]* ]] && [ $1 -ge 0 ] && [ $1 -le 100 ]; Вы также можете обойти циклы for и сократить код с помощью printf и подстановки команд: printf "["; printf "%.0=" $(seq $val); printf "%.0." $(seq $[ $val+1 ] $max); printf "] %s%%\r" $val;
-4

У меня было то же самое, что и сегодня, и на основе ответа Диомидиса, вот как я это сделал (linux debian 6.0.7). Возможно, это могло бы помочь вам:

#!/bin/bash

echo "getting script inode"
inode=`ls -i ./script.sh | cut -d" " -f1`
echo $inode

echo "getting the script size"
size=`cat script.sh | wc -c`
echo $size

echo "executing script"
./script.sh &
pid=$!
echo "child pid = $pid"

while true; do
        let offset=`lsof -o0 -o -p $pid | grep $inode | awk -F" " '{print $7}' | cut -d"t" -f 2`
        let percent=100*$offset/$size
        echo -ne " $percent %\r"
done
  • 0
    Можете ли вы объяснить, что рассчитать смещение.
  • 0
    Когда я запускаю этот скрипт от имени пользователя root, я получаю сообщение об ошибке: lsof: WARNING: can't stat() fuse.gvfsd-fuse file system /home/rubo77/.gvfs Output information may be incomplete.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню