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

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

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

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

Ответы [ 36 ]

0 голосов
/ 10 октября 2014

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

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

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

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

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

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

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

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

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

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

progress_filter() {

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

        rm -f ${PROGRESS}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Примеры:

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

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

echo "scp"
scp user@192.168.1.1:~/myfile.tar /dev/stdout | progress_filter
0 голосов
/ 27 марта 2015

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

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

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

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

PERCRATE=100.0/TOTTIME
BARRATE=BARSIZE/TOTTIME

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

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

python progressbar.py 10 50

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

0 голосов
/ 09 июля 2016

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

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

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

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

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

Кстати: я использую bash на Mac OSX. Я основал этот код на счетчике из mariascio .

0 голосов
/ 13 июня 2016

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

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

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

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

Код:

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

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

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

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

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

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

0 голосов
/ 17 мая 2016

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

Внутренняя функция 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 голосов
/ 10 февраля 2016

Я основывался на ответе от страха

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