BASH:在一行打印输出

时间:2015-05-03 17:39:22

标签: bash

我已经在下面使用这个简单的脚本将压缩的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开关在这种情况下无效。

编辑:2015年5月6日星期三15:17:29 BST 2015

我能够根据公认的答案达到预期的结果:

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

- 然而,在子shell中运行的命令没有将退出状态返回到父shell,因为它并行运行,因此我无法验证它是成功还是失败。

7 个答案:

答案 0 :(得分:6)

我认为这个命令会做你想要的:

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结果完成。子shell ( … )和错误重定向2>&1确保错误消息也进入回显输出。 $(之后的空格是必要的,因为没有空格的$((是一个不同的特殊操作 - 算术扩展。

答案 1 :(得分:5)

感谢您的所有帮助,但我想我终于找到了最佳解决方案。

基本上我使用xargs来格式化输出,因此每个条目(转储名称+持续时间)都在一行上。我还将作业规范添加到wait命令以获取退出状态:

man bash

  等等[n ...]       等待每个指定的进程并返回其终止状态。每个n可以是进程ID或作业规范;如果是工作规范   给定,该作业管道中的所有进程都在等待。如果是   没有给出,所有当前活动的子进程都在等待,并且   返回状态为零。如果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

最终剧本:

#!/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 :(得分:3)

扩展您的答案,在失败时立即退出脚本,您必须将后台进程的pid保存在数组中。在while循环中,在pids[COMMIT_COUNT]=$!命令后添加mysqldump

然后你可以写一个函数来遍历所有这些pid,如果其中一个失败则退出:

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)

备注

你可以在for循环中用jobs -p替换pids数组,但是在调用循环之前你不会得到完成的作业的pids。

上面的wait_jobs()函数不能在子shell中使用,exit 1调用只会终止子shell。

完整的脚本:

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

答案 3 :(得分:1)

关于退出状态的其他问题,让我再写一个答案。因为$()将运行子shell,我不认为可以像普通命令一样将退出状态返回到主shell。但是可以将退出状态写入稍后要检查的文件。请尝试以下命令。它将创建包含两行的名为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文件。

答案 4 :(得分:0)

我会创建单独的函数来控制所有进程,然后在后台运行此函数,而不是运行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 pipline。 顺便说一下,这里有一点点改进。当您达到限制时,您可能不希望等待所有生成的进程。等待任意一个就足够了。这就是wait -n 1做的事情

答案 5 :(得分:0)

您尝试使用脚本进行并行化。我建议不要重新发明轮子,而是使用久经考验的工具:GNU parallel。该教程非常庞大:http://www.gnu.org/software/parallel/parallel_tutorial.html

对于以退出值返回的作业,它有不同的选项!= 0:第一个错误中止或继续工作直到结束。

与OP脚本并行的GNU的一个优点是,它可以在第一个作业完成后立即启动第三个作业。

答案 6 :(得分:-3)

未经测试等。

#!/bin/sh

COMMIT_COUNT=0
COMMIT_LIMIT=2

_dump() {
  # better use gzip or xz. There's 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