使用嵌套循环检查到外壳程序中两个服务器的连接

时间:2018-09-03 00:18:50

标签: shell docker sh alpine

我在docker-compose中定义了docker环境。 它有3个容器。两个带有数据库,第三个带有cli脚本解释器。

第三个容器必须等待2个db容器接受连接。

当我只有一个数据库容器时,这非常简单。 第三个容器(基于Alpine)entrypoint.sh:

#!/bin/sh
while ! nc -z $MYSQL_HOST $MYSQL_PORT; do
    echo "Waiting for MySQL to accept connections ..."
    sleep 0.2
done
echo "MYSQL ready"

exec "$@"

现在有了第二个容器,我想使用嵌套循环来解决这个问题,但是由于posix shell通常不支持数组,所以我不知道该怎么做。

通常的想法是使用host1:port1创建一个字符串,并用空格分隔

#!/bin/sh
SERVERS="DB1_HOST:DB1_PORT DB2_HOST:DB2_PORT"

通过使用空格(我认为IFS ='')创建项目来遍历它,而不是使用DB:PORT来检查使用nc的连接,并在可能建立连接时从SERVERS变量中删除DB:PORT。

我主要担心的是,我不知道如何从SERVERS变量中删除DB:PORT。

3 个答案:

答案 0 :(得分:1)

这是您发布的答案的重构,避免了某些特质和效率低下。

#!/bin/sh
: ${MYSQL_HOST?must not be unset}
: ${MYSQL_PORT?must not be unset}
: ${REDIS_HOST?must not be unset}
: ${REDIS_PORT?must not be unset}

# avoid uppercase for private variables
# avoid gratuitous non-portable newline
# pad with surrounding spaces
servers=" MYSQL_HOST:MYSQL_PORT REDIS_HOST:REDIS_PORT "

while true; do
    for server in $servers; do
        db_host=${server%:*}
        db_port=${server#*:}
        if nc -z "$db_host" "$db_port"; then
            # Show diagnostic messages on stderr
            echo "$0: Host '$db_host' started accepting connections" >&2
            servers="${servers#* $db_host:$db_port } ${servers% $db_host:$db_port *}"
        else
            echo "$0: Waiting for host '$db_host' to start accepting connections on port '$db_port' ..." >&2
        fi
    done
    case $servers in
      *[! ]*) ;;
      *) break
    esac
    sleep 0.4
done

使用单个以空格分隔的字符串进行循环有点麻烦,但是至少我们在这里避免使用外部进程来操纵服务器列表。在没有数组变量的情况下,没有真正好的方法来选择性地删除某些服务器并保留其他服务器。另外,由于空格的原因,servers字符串在删除所有服务器后不一定会为空,因此我们在循环内部检查它是否包含非空格字符。

接受服务器作为命令行参数(也许然后直接以host:port形式)可能比要求调用者设置多个变量(更不用说对这些变量使用大写字母)更有意义。然后遍历"$@"也许也会使操纵服务器列表更简单。

我将带您到http://shellcheck.net/来诊断许多错误,而无需人工干预,而http://www.iki.fi/era/unix/award.html#echo会指出一些常见的反模式。 parameter expansion上的POSIX sh规范解释了上面代码中的许多构造。 Bash manual section on parameter expansion显然记录了Bash,但也可以用作辅助来源。

答案 1 :(得分:0)

您可以将值存储为函数参数:

#!/bin/sh

sqlup() {
    for server do
        IFS=: read -r host port

        while ! nc -z "$host" "$port"; do
            printf 'Waiting for MySQL to accept connections...\n'
            sleep 0.2
        done
    done

    printf 'MySQL ready\n'
}

sqlup DB1_HOST:DB1_PORT DB2_HOST:DB2_PORT # ...
exec "$@"

答案 2 :(得分:0)

有效的解决方案。使用ASH在Linux Alpine 3.7上进行测试

如果有人知道脚本为什么被破坏(检查代码中以#开头的行,那么请发表评论〜谢谢)

#!/bin/sh
MYSQL_HOST=${MYSQL_HOST:-3}
MYSQL_PORT=${MYSQL_PORT:-3}
REDIS_HOST=${REDIS_HOST:-3}
REDIS_PORT=${REDIS_PORT:-3}

REQ_VARS="MYSQL_HOST\nMYSQL_PORT\nREDIS_HOST\nREDIS_PORT"

printf "$REQ_VARS" | while IFS='' read -r VAR
do
    value=$(eval "echo \$$VAR")
    if [[ ${value} == 3 ]]; then
        echo "Error: Variable '$VAR' cannot be undefined"
        exit 1
    fi
done

SERVERS="MYSQL_HOST:MYSQL_PORT\nREDIS_HOST:REDIS_PORT"

while test ! -z "$SERVERS"
do
    for server in $(printf "$SERVERS"); do
        while IFS=: read HOST PORT; do
            DB_HOST=$(eval "echo \$$HOST")
            DB_PORT=$(eval "echo \$$PORT")

            if nc -z $DB_HOST $DB_PORT; then
                echo "Host '$DB_HOST' started accepting connections"

                # When removing last server from SERVERS script was terminating
                if [ "$(printf "$SERVERS" | wc -l)" -gt 0 ]; then
                    SERVERS=$(printf "$SERVERS" | grep -iv "$HOST:$PORT")
                else
                    SERVERS=""
                fi

                continue
            fi

            echo "Waiting for host '$DB_HOST' to start accepting connections on port '$DB_PORT' ..."

        done <<EOF
$(printf "$server")
EOF

    done

    # Wait 0.4 of the second before checking again
    sleep 0.4
done