调用函数时Bash脚本挂起

调用函数时Bash脚本挂起,bash,shell,freeze,Bash,Shell,Freeze,我目前正在测试一个bash脚本来执行数据库迁移 脚本基本上接受一些参数,例如: 要迁移的数据库的名称 从中进行迁移的服务器 将其迁移到的服务器 脚本中有一个函数,它“构建”要执行的mysql和mysqldump命令,具体取决于from/to服务器是否为本地/远程服务器 然后使用函数build\u mysql\u命令,如下所示: _query="$(build_mysql_command mysql from)" _dump="$(build_mysql_command mysqldump f

我目前正在测试一个bash脚本来执行数据库迁移

脚本基本上接受一些参数,例如:

  • 要迁移的数据库的名称
  • 从中进行迁移的服务器
  • 将其迁移到的服务器
脚本中有一个函数,它“构建”要执行的mysql和mysqldump命令,具体取决于from/to服务器是否为本地/远程服务器

然后使用函数
build\u mysql\u命令
,如下所示:

_query="$(build_mysql_command mysql from)"
_dump="$(build_mysql_command mysqldump from)"
_restore="$(build_mysql_command mysql to)"
但是,当函数
build\u mysql\u命令
必须调用
open\u ssh\u tunnel
时,它将挂起最后一条指令,正如我使用带有
-x
开关的脚本所测试的那样

相反,如果我将SSH隧道的开口放在
build\u mysql\u命令
之外,并从那里删除调用,它就会工作

但是,我不认为我在上述函数中犯了任何错误,因此我不理解为什么脚本会挂起

下面是一个非常简单的示例,显示了问题所在,我将远程服务器的实际IP地址替换为
1.2.3.4

#!/bin/bash
set -x
set -o pipefail

# $1 = 'from' or 'to'
get_local_port() {
    case "$1" in
        from)
            echo 30303
        ;;

        to)
            echo 31313
        ;;

        *)
            echo 0
        ;;
    esac
}

# $1 = 'from' or 'to'
build_ssh_command() {
    local _ssh="ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no"

    if [ ! -z "${params[password-$1]}" ] ; then
        _ssh="sshpass -f ${params[password-$1]} $_ssh"
    fi

    echo "$_ssh"
}

# $1 = 'from' or 'to'
open_ssh_tunnel() {
    # se non già aperto
    if [ -z "${cleanup[ssh-tunnel-$1]}" ] ; then
        local _port="$(get_local_port "$1")"
        local _ssh="$(build_ssh_command "$1")"
        local _pregp="fnNTL $_port:localhost:3306 ${params[migrate-$1]}"
        local _command="$_ssh -$_pregp"

        # tento apertura tunnel SSH
        if ! $_command ; then
            return 1
        else
            # salvo PID del tunnel così aperto
            local _pid="$(pgrep -f "$_pregp" 2> /dev/null)"

            if [ -z "$_pid" ] ; then
                return 1
            fi

            cleanup["ssh-tunnel-$1"]="$_pid"
        fi
    fi

    return 0
}

# verifica se un indirizzo fa riferimento alla macchina locale
# $1 = indirizzo da verificare
is_local_address() {
    local _host="$(hostname)"

    case "$1" in
        localhost|"127.0.0.1"|"$_host")
            return 0
        ;;

        *)
            return 1
        ;;
    esac
}

# costruisce un comando di dump o restore MySQL
# $1 = comando di base
# $2 = tipo server ('from' o 'to')
build_mysql_command() {
    local _command="$1 --user=root --password=xxx"

    if is_local_address "${params[migrate-$2]}" ; then
        # connessione tramite socket
        _command="$_command --protocol=socket --socket=/opt/agews64/data/mysql/mysql.sock"
    elif open_ssh_tunnel "$2" ; then
        # altrimenti uso connessione tramite tunnel SSH
        _command="$_command --protocol=tcp --host=localhost --port=$(get_local_port "$2")"
    else
        _command=""
    fi

    echo "$_command"
}

# parametri di esecuzione dello script
declare -A params=(
    ["migrate-from"]="localhost"
    ["migrate-to"]="1.2.3.4"
)

_query="$(build_mysql_command "mysql" "from")"
echo "_query = $_query"

_dump="$(build_mysql_command "mysqldump" "to")"
echo "_dump = $_dump"

# fine test
以下是运行时的输出:

+ set -o pipefail
+ params=(["migrate-from"]="localhost" ["migrate-to"]="1.2.3.4")
+ declare -A params
++ build_mysql_command mysql from
++ local '_command=mysql --user=root --password=xxx'
++ is_local_address localhost
+++ hostname
++ local _host=my.host.name
++ case "$1" in
++ return 0
++ _command='mysql --user=root --password=xxx --protocol=socket --socket=/opt/agews64/data/mysql/mysql.sock'
++ echo 'mysql --user=root --password=xxx --protocol=socket --socket=/opt/agews64/data/mysql/mysql.sock'
+ _query='mysql --user=root --password=xxx --protocol=socket --socket=/opt/agews64/data/mysql/mysql.sock'
+ echo '_query = mysql --user=root --password=xxx --protocol=socket --socket=/opt/agews64/data/mysql/mysql.sock'
_query = mysql --user=root --password=xxx --protocol=socket --socket=/opt/agews64/data/mysql/mysql.sock
++ build_mysql_command mysqldump to
++ local '_command=mysqldump --user=root --password=xxx'
++ is_local_address 1.2.3.4
+++ hostname
++ local _host=asp10.626suite-online.it
++ case "$1" in
++ return 1
++ open_ssh_tunnel to
++ '[' -z '' ']'
+++ get_local_port to
+++ case "$1" in
+++ echo 31313
++ local _port=31313
+++ build_ssh_command to
+++ local '_ssh=ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no'
+++ '[' '!' -z '' ']'
+++ echo 'ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no'
++ local '_ssh=ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no'
++ local '_pregp=fnNTL 31313:localhost:3306 1.2.3.4'
++ local '_command=ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no -fnNTL 31313:localhost:3306 1.2.3.4'
++ ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no -fnNTL 31313:localhost:3306 1.2.3.4
Warning: Permanently added '1.2.3.4' (ECDSA) to the list of known hosts.
+++ pgrep -f 'fnNTL 31313:localhost:3306 1.2.3.4'
++ local _pid=8919
++ '[' -z 8919 ']'
++ cleanup["ssh-tunnel-$1"]=8919
++ return 0
+++ get_local_port to
+++ case "$1" in
+++ echo 31313
++ _command='mysqldump --user=root --password=xxx --protocol=tcp --host=localhost --port=31313'
++ echo 'mysqldump --user=root --password=xxx --protocol=tcp --host=localhost --port=31313'

正如您所看到的,当脚本打开到远程服务器的SSH隧道时,它挂起在
build_mysql_命令的最后一行,但在生成本地命令时没有显示问题。

与您的问题无关,但不要将命令作为字符串生成;它天生就容易失败,最终你会达到极限。请看……谈到眼前的问题本身,运行ssh命令会阻塞,直到该命令退出(如果它在到达该点之前没有阻塞读取stdin),这并不奇怪。令人惊讶的是,我们希望看到这个命令实际上是什么,以及完整的参数列表;提供
集合-x
跟踪的尾部是一个开始。函数中的最后一条指令是
返回0
,它是挂起的吗?我同意,查看跟踪会有帮助。我根据您的评论添加了更多详细信息。@Sheller我添加了一个脚本的精简版本,该版本仍然显示问题,其输出与您的问题无关,但不要将命令构建为字符串;它天生就容易失败,最终你会达到极限。请看……谈到眼前的问题本身,运行ssh命令会阻塞,直到该命令退出(如果它在到达该点之前没有阻塞读取stdin),这并不奇怪。令人惊讶的是,我们希望看到这个命令实际上是什么,以及完整的参数列表;提供
集合-x
跟踪的尾部是一个开始。函数中的最后一条指令是
返回0
,它是挂起的吗?我同意,查看跟踪会有帮助。我根据您的评论添加了更多详细信息。@Shelleter我添加了一个脚本的精简版本,该版本仍然显示问题,并使用
set+x