Bash 引用进阶
我之前写过关于 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 |
+----+-------------------------------+---------------------+
我们是否已经有足够的引用了???