【问题标题】:Checking connection to two servers in shell using nested loops使用嵌套循环检查与 shell 中两台服务器的连接
【发布时间】:2019-02-08 01:11:16
【问题描述】:

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

第 3 个容器必须等待 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。

【问题讨论】:

    标签: shell docker sh alpine


    【解决方案1】:

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

    #!/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 "$@"
    

    【讨论】:

    • 我已经在下面发布了答案。
    • 你的解决方案有很多不好的做法,但是没关系。
    • 我会很感激指出他们。我看过@triplee 的答案,代码看起来比我的好得多,但我仍然不知道你在说什么不好的做法。
    • 只是一个小例子:value=$(eval "echo \$$VAR")。没有理由不能只是value=$VAR
    • 它不能,因为在这种情况下 $VAR 持有被检查变量的“名称”。
    【解决方案2】:

    工作解决方案。使用 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
    

    【讨论】:

    • 您可能想探索${variable?must not be unset}
    • 不幸的是,我在谷歌上找不到任何东西。请分享正确的搜索词组。谢谢
    • 现在查看单独的答案。顺便说一句,您的代码有一个错误:如果您的服务器的名称是另一个服务器的子字符串,则当较短的服务器响应时,您将两者都删除。 (host:11otherhost:111 的子串)
    【解决方案3】:

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

    #!/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,但也可能用作辅助来源。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-10-20
      • 1970-01-01
      • 2021-03-15
      • 2019-10-17
      • 1970-01-01
      • 2019-01-14
      • 2023-04-11
      相关资源
      最近更新 更多