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) &
- однако команда, запущенная в подоболочке, не возвращает статус завершения родительской оболочке, потому что она работает параллельно, поэтому я не могу проверить, выполнено ли это или не удалось.
Ответы
Ответ 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