Подтвердить что ты не робот

BASH: вывод печати на одну строку

У меня есть этот простой script ниже для потоковой передачи сжатых MySQL-дампов в ведро Amazon S3 параллельно:

#!/bin/bash

COMMIT_COUNT=0
COMMIT_LIMIT=2

for i in $(cat list.txt); do

        echo "$i "

        mysqldump -B $i | bzip2 -zc | gof3r put -b s3bucket -k $i.sql.bz2 &


        (( COMMIT_COUNT++ ))

        if [ ${COMMIT_COUNT} -eq ${COMMIT_LIMIT} ]; then
        COMMIT_COUNT=0
        wait
        fi

done

if [ ${COMMIT_COUNT} -gt 0 ]; then
        wait
fi

Результат выглядит следующим образом:

database1 
database2 
duration: 2.311823213s
duration: 2.317370326s

Есть ли способ распечатать это на одной строке для каждого дампа?

database1 - duration: 2.311823213s
database2 - duration: 2.317370326s

В этом случае переключатель echo -n не помогает.

РЕДАКТИРОВАТЬ: ср. 6 мая 15:17:29 BST 2015

Я смог достичь ожидаемых результатов на основе принятого ответа:

echo "$i -" $(mysqldump -B $i| bzip2 -zc | gof3r put -b s3bucket -k $i.sql.bz2 2>&1) &

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

4b9b3361

Ответ 1

Спасибо за вашу помощь, но я думаю, что наконец нашел оптимальное решение для этого.

В основном я использовал xargs для форматирования вывода, чтобы каждая запись (имя дампа + время продолжительности) находилась в одной строке. Я также добавил спецификацию задания к команде wait, чтобы получить статус выхода:

man bash

wait [n...]     Подождите каждого указанного процесса и верните его статус завершения. Каждый n может быть идентификатором процесса или спецификацией задания; если спецификация работы данный, все процессы в этом рабочем трубопроводе ждут. Если n не заданы, все активные активные процессы в настоящее время ждут, и статус возврата равен нулю. Если n указывает несуществующий процесс или задание, статус возврата равен 127. В противном случае статус возврата является статус выхода последнего процесса или задание, ожидаемое.

Тест:

# sh -c 'sleep 5; exit 1' &
[1] 29970
# wait; echo $?
0
# sh -c 'sleep 5; exit 1' &
[1] 29972
# wait $(jobs -p); echo $?
1

Финал script:

#!/bin/bash

COMMIT_COUNT=0
COMMIT_LIMIT=2


while read -r i; do

    mysqldump -B $i | bzip2 -zc | gof3r put -b s3bucket -k $i.sql.bz2 |& xargs -I{} echo "${DB} - {}" &

    (( COMMIT_COUNT++ ))

    if [ ${COMMIT_COUNT} -eq ${COMMIT_LIMIT} ]; then
        COMMIT_COUNT=0
        wait $(jobs -p)
    fi

done < list.txt

if [ ${COMMIT_COUNT} -gt 0 ]; then
     wait $(jobs -p)
fi

if [ $? -ne 0 ]; then
     echo "ERROR: Backups failed"
     exit 1
fi  

Ответ 2

Я думаю, что эта команда будет делать то, что вы хотите:

echo "$i -" `(mysqldump -B $i | bzip2 -zc | gof3r put -b s3bucket -k $i.sql.bz2) 2>&1` &

Или используйте $() вместо обратных ссылок:

echo "$i -" $( (mysqldump -B $i| bzip2 -zc | gof3r put -b s3bucket -k $i.sql.bz2) 2>&1 ) &

Команда echo будет ждать завершения результата mysqldump .., прежде чем пытаться распечатать вместе с $i. Sub-shell ( … ) и перенаправление ошибок 2>&1 гарантируют, что сообщения об ошибках также войдут в эхо-результат. Пространство после $( необходимо, так как $(( без пробела - другая специальная операция - арифметическое разложение.

Ответ 3

Развернув ответ, чтобы выйти из script сразу после сбоя, вам нужно сохранить pids фоновых процессов в массиве. В цикле while добавьте pids[COMMIT_COUNT]=$! после команды mysqldump.

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

wait_jobs() {
    for pid in "${pids[@]}"; do
        wait ${pid}
        if [ $status -ne 0 ]; then
            echo "ERROR: Backups failed"
            exit 1
        fi
    done
}

Вызовите эту функцию вместо wait $(jobs -p) в script.

Примечания

Вы можете заменить массив pids на jobs -p в цикле for, но тогда вы не получите pids заданий, которые были выполнены до вызова цикла.

Вышеуказанная функция wait_jobs() не может использоваться в подоболочке, вызов exit 1 будет только завершать подоболочку.


Полный script:

#!/bin/bash

COMMIT_COUNT=0
COMMIT_LIMIT=2

wait_jobs() {
    for pid in "${pids[@]}"; do
        wait ${pid}
        if [ $status -ne 0 ]; then
            echo "ERROR: Backups failed"
            exit 1
        fi
    done
}

while read -r i; do

    mysqldump -B $i | bzip2 -zc | gof3r put -b s3bucket -k $i.sql.bz2 |& xargs -I{} echo "${DB} - {}" &
    # save the pid of the background job so we can get the
    # exit status with wait $pid later
    pids[COMMIT_COUNT]=$!

    (( COMMIT_COUNT++ ))

    if [ ${COMMIT_COUNT} -eq ${COMMIT_LIMIT} ]; then
        COMMIT_COUNT=0
        wait_jobs
    fi

done < list.txt

wait_jobs

Ответ 4

Что касается вашего дополнительного вопроса о статусе выхода, позвольте мне написать еще один ответ. Поскольку $() будет запускать подоболочку, я не думаю, что можно вернуть статус выхода в основную оболочку, как обычно. Но можно записать статус выхода в файл, который будет рассмотрен позже. Пожалуйста, попробуйте выполнить команду ниже. Он создаст файл с именем status-$i.txt, содержащий две строки. Один для mysqldump, другой для gof3r.

e="status-$i.txt"
echo -n > $e

echo "$i -" $( \
      ( mysqldump -B $i 2>&1; echo m=$? >> $e ) \
    |   bzip2 -zc \
    | ( gof3r put -b s3bucket -k $i.sql.bz2 2>&1; echo g=$? >> $e ) \
) &

Вам также может понадобиться очистить все status-*.txt файлы в начале вашего script.

Ответ 5

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

При этом у вас будет несколько процессов, работающих одновременно, и в то же время у вас будет контроль над mysqldump, поскольку он запускался синхронно

#!/bin/bash

do_job(){
    param=$1
    echo job $param started... >&2  # Output to stderr as stdout is grabbed
    sleep $[$RANDOM/5000]
    echo $RANDOM  # Make some output
    [ $RANDOM -ge 16383 ]  # Generate exit code
}

control_job() {
    param=$1
    output=`do_job $param`
    exit_code=$?
    echo $1 printed $output and exited with $exit_code
}

JOBS_COUNT=0
JOBS_LIMIT=2

for i in database1 database2 database3 database4; do

    control_job $i &

    (( JOBS_COUNT++ ))

    if [ $JOBS_COUNT -ge $JOBS_LIMIT ]; then
        (( JOBS_COUNT-- ))
        wait -n 1  # wait for one process to exit
    fi

done

wait  # wait for all processes running

Здесь do_job используется вместо вашей линии mysqldump. Кстати, там небольшое улучшение. Вероятно, вы не хотите ждать всех порожденных процессов, когда вы достигли предела. Достаточно дождаться произвольного. То, что wait -n 1 делает

Ответ 6

Вы пытаетесь выполнить распараллеливание с помощью script. Я бы рекомендовал не изобретать колесо, но использовать проверенный и проверенный инструмент: GNU parallel. Учебник огромен: http://www.gnu.org/software/parallel/parallel_tutorial.html

У него разные варианты для заданий, возвращающихся с выводом!= 0: прервать первую ошибку или продолжить работу до конца.

Одним из преимуществ GNU, параллельным script OP, является то, что он сразу же запускает третье задание сразу после завершения первого.

Ответ 7

untested и т.д.

#!/bin/sh

COMMIT_COUNT=0
COMMIT_LIMIT=2

_dump() {
  # better use gzip or xz. There no benefit using bzip2 afaict
  output="$(mysqldump -B "$1" | bzip2 -zc | gof3r put -b s3bucket -k "$1.sql.bz2" 2>&1)"
  [ "$?" != 0 ] && output="failed"
  printf "%s - %s\n" "$1" "$output"
}

while read i; do
  _dump "$i" &

  (( COMMIT_COUNT++ ))

  if [ ${COMMIT_COUNT} -eq ${COMMIT_LIMIT} ]; then
    COMMIT_COUNT=0
    wait
  fi
done < list.txt

wait