使用嵌套循环检查shell中两个服务器的连接

问题描述 投票:0回答:3

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

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

当我只有一个数据库容器时,它非常简单。第3个容器(基于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。

shell docker sh alpine
3个回答
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

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

接受服务器作为命令行参数(可能直接在主机:端口形式)可能更有意义,而不是要求调用者设置多个变量(更不用说那些变量使用大写)。然后循环"$@"也许会使操作服务器列表变得更简单。

我会指向http://shellcheck.net/,它可以在没有人为干预的情况下诊断出许多这些错误,http://www.iki.fi/era/unix/award.html#echo指出了一些常见的反模式。关于sh的POSIX parameter expansion规范解释了上面代码中的许多构造。 Bash manual section on parameter expansion显然记录了Bash,但也可能作为辅助来源。


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 "$@"

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
© www.soinside.com 2019 - 2024. All rights reserved.