Bash 引用进阶

作者:Mitch Frazier

我之前写过关于 bash 引用的文章,是的,它和看着油漆变干或者听着玉米生长一样令人兴奋。但当它没有按照你想要的方式工作时,它也会非常令人沮丧,例如:尝试编写脚本来更新 mysql 表中的字段,而要更改的字段包含引号字符。

假设我们有一个简单的数据表,数据如下,我们想要更改 name 字段。

+----+----------------------+---------------------+
| id | name                 | create_date         |
+----+----------------------+---------------------+
|  1 | name 'with' "quotes" | 2009-11-19 08:48:59 |
+----+----------------------+---------------------+

您的第一个脚本可能看起来像这样

#!/bin/bash

USERNAME=${USERNAME:-user}
PASSWORD=${PASSWORD:-pwd}

mysql_cmd="mysql -u $USERNAME -p$PASSWORD test"

# Remove mysql header line.
function remove_header()
{
    shift
    echo $*
}

id=1
name=$(remove_header $($mysql_cmd -e "SELECT name FROM atable WHERE id='$id'"))

new_name="$name and more"
echo mysql -e "UPDATE atable SET name='$new_name' WHERE id='$id'"
$mysql_cmd -e "UPDATE atable SET name='$new_name' WHERE id='$id'"

# vim: tabstop=4: shiftwidth=4: noexpandtab:
# kate: tab-width 4; indent-width 4; replace-tabs false;

当您运行它时,它会抛出如下错误

$ bash badfix.sh
mysql -e UPDATE atable SET name='name 'with' quotes and more' WHERE id='1'
ERROR 1064 (42000) at line 1:
    You have an error in your SQL syntax;
    check the manual that corresponds to your MySQL server version
    for the right syntax to use near 'with' quotes and more' WHERE id='1''
    at line 1

请注意,顶部的函数 (remove_header) 从 mysql 输出中删除标题行,这样我们就不会在数据中包含字段的名称。

我们都知道这里的解决方案:我们需要转义值中的引号,以便 bash 和 mysql 都能正常工作。然而,事实证明这说起来容易做起来难,也许我错过了显而易见的方法,但在多次尝试(不止一次)之后,以下方法最终奏效

#!/bin/bash

USERNAME=${USERNAME:-user}
PASSWORD=${PASSWORD:-pwd}

mysql_cmd="mysql -u $USERNAME -p$PASSWORD test"

# Remove mysql header line.
function remove_header()
{
    shift
    echo $*
}

# Quote any quotes in a mysql value.
function fix_quotes()
{
    local  val="$*"
    if [[ "$val" =~ .*\'.* ]]; then #'
        echo "String contains single quotes: $val" >&2
        #val="${val//'/\\\\'}"
        val=$(sed -e "s/'/' \"'\" '/g" <<<"$val")
        echo New Value: "$val" >&2
    fi
    echo "$val"
}



id=1
name=$(remove_header $($mysql_cmd -e "SELECT name FROM atable WHERE id='$id'"))

fixed_name="$(fix_quotes "$name") and more"
echo mysql -e "UPDATE atable SET name='$fixed_name' WHERE id='$id'"
$mysql_cmd -e "UPDATE atable SET name='$fixed_name' WHERE id='$id'"

# vim: tabstop=4: shiftwidth=4: noexpandtab:
# kate: tab-width 4; indent-width 4; replace-tabs false;

这个fix_quotes函数只检查单引号,因为我们的 mysql 值包含在单引号中。

$mysql_cmd -e "UPDATE atable SET name='$fixed_name' WHERE id='$id'"
#                                     ^           ^

正如您所期望的,我们不需要转义 mysql 单引号内的双引号。但是,如果我们想在 SQL 命令中使用字面值,我们需要转义双引号,因为我们的 SQL 命令包含在双引号中。

$mysql_cmd -e "UPDATE atable SET name='quoted \"value\"' WHERE id='$id'"
#             ^                                                        ^

在这种情况下,我们需要为 bash 转义它们,而不是为 mysql:bash 会在将命令传递给 mysql 之前“删除”反斜杠。

我的最初尝试之一(您可以在代码中看到注释掉的部分)是尝试使用 bash 赋值语句直接更改值。我试图将每个单引号更改为转义的单引号。

val="${val//'/\\\\'}"

有趣的是,这根本没有修改字符串,我不太理解这个结果。 我尝试使用类似的sed命令,但也没有奏效。 最终奏效的解决方案是基于 mysql(像 C++ 一样)将相邻字符串连接成单个字符串的事实。 所以,我改变(使用sedsed)字符串内的所有单引号变成这个序列:单引号,空格,双引号,单引号,双引号,空格,单引号。 您可能会注意到双引号被转义了,但这仅仅是为了在 `sed` 命令中进行转义,这些转义不会进入传递给 mysql 的值中。sed命令中进行转义,这些转义不会进入传递给 mysql 的值中。

val=$(sed -e "s/'/' \"'\" '/g" <<<"$val")

运行最终版本会奏效

$ bash fix.sh
String contains single quotes: name 'with' \"quotes\"
New Value: name ' "'" 'with' "'" ' \"quotes\"
mysql -e UPDATE atable SET name='name ' "'" 'with' "'" ' \"quotes\" and more' WHERE id='1'

您可以在表中看到结果。

+----+-------------------------------+---------------------+
| id | name                          | create_date         |
+----+-------------------------------+---------------------+
|  1 | name 'with' "quotes" and more | 2009-11-19 08:48:59 |
+----+-------------------------------+---------------------+

我们是否已经有足够的引用了???

Mitch Frazier 是艾默生电气公司的嵌入式系统程序员。 Mitch 自 2000 年代初以来一直是 Linux Journal 的贡献者和朋友。

加载 Disqus 评论