Как добавить индикатор выполнения в сценарий оболочки? - PullRequest
361 голосов
/ 26 октября 2008

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

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

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

Ответы [ 36 ]

7 голосов
/ 03 мая 2013

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

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

4 голосов
/ 24 августа 2010

Мое решение отображает процент тарбола, который в настоящее время распаковывается и пишется. Я использую это при записи 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 голосов
/ 18 июля 2014

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

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

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

Обновление:

Сама команда

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

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

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

4 голосов
/ 26 октября 2008

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

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

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

3 голосов
/ 04 декабря 2018

Индикатор выполнения в стиле 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}"
}
3 голосов
/ 10 января 2016

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

3 голосов
/ 30 сентября 2018

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

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

[##################################################] 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"
2 голосов
/ 08 апреля 2016

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

для меня самый простой в использовании и наиболее привлекательный вид - это команда 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 голос
/ 29 июня 2015

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

...