SED:sh 和 bash 中的奇怪行为,zsh 中的正常行为

SED: Strange behavior in sh and bash, ok in zsh

提问人:Greg'ory 提问时间:11/14/2023 最后编辑:tripleeeGreg'ory 更新时间:11/15/2023 访问量:91

问:

我对命令有一个奇怪的行为。sed

首先,我关心的是 Bash 脚本中有一个命令,使我能够替换位于字符串之后的字符串。sedsas_token

文件如下:

storage_account_name = "stoftfstate"
container_name = "tfstate-ct"
key = "terraform.tfstate"
sas_token = "se=2023-11-21T09%3A51Z&sp=rwl&sip=94.233.160.130&spr=https&sv=2022-11-02&sr=c&sig=9G5tqn7RZxagUgnvpenBGm%2BNQVipRYOFxjz6l6mrvtg%3D"

然后,我生成一个新令牌,我想在上面的文件中替换该令牌。 我想使用 . 由于我生成的令牌包含 & 符号(),因此我必须使用子字符串替换,如下所示:sed&

${NEW_TOKEN//&/\\&}

这给了我们这个命令:sed

sed -i 's/\(^sas_token =\)\(\s*.*$\)/\1 "'${NEW_TOKEN//&/\\&}'"/' .ENV/app-backend.tfvars

但它不在 or 环境中工作,只在 .但目标是仅在其中实现这一目标。bashshzshbash

请在下面找到完整的解释:

测试方式:bash

检查口译员:

minime@yd-202203101028:~/terraform$ ps -ef | grep $$ | grep -v grep
minime 15853    3349  0 11:23 pts/2    00:00:00 bash
minime 16045   15853  0 11:25 pts/2    00:00:00 ps -ef

修改前检查文件:

minime@yd-202203101028:~/terraform$ less -F .ENV/app-backend.tfvars
storage_account_name = "stoftfstate"
container_name = "tfstate-ct"
key = "terraform.tfstate"
sas_token = "se=2023-11-21T09%3A51Z&sp=rwl&sip=94.233.160.130&spr=https&sv=2022-11-02&sr=c&sig=9G5tqn7RZxagUgnvpenBGm%2BNQVipRYOFxjz6l6mrvtg%3D"

检查新字符串以替换行:sas_token

minime@yd-202203101028:~/terraform$ echo ${TEST_STRING}
se=1998-11-21T08%3A49Z&sp=rwl&sip=193.253.170.130&spr=https&sv=2022-11-02&sr=c&sig=DE7xk1ggg%3D

运行:sed

minime@yd-202203101028:~//terraform$ sed 's/\(^sas_token =\)\(\s*.*$\)/\1 "'${TEST_STRING//&/\\&}'"/' .ENV/app-backend.tfvars
storage_account_name = "stoftfstate"
container_name = "tfstate-ct"
key = "terraform.tfstate"
sas_token = "se=2023-11-21T09%3A51Z&sp=rwl&sip=94.233.160.130&spr=https&sv=2022-11-02&sr=c&sig=9G5tqn7RZxagUgnvpenBGm%2BNQVipRYOFxjz6l6mrvtg%3D"

看?该值没有改变...sas_token

测试方式:zsh

检查口译员:

minime@yd-202203101028:~/terraform  ps -ef | grep $$ | grep -v grep
mini-me    3349    3329  0 09:20 pts/2    00:00:02 /usr/bin/zsh -i
mini-me   16618    3349  0 11:42 pts/2    00:00:00 ps -ef

修改前检查文件:

minime@yd-202203101028:~/terraform  less -F .ENV/app-backend.tfvars
storage_account_name = "stoftfstate"
container_name = "tfstate-ct"
key = "terraform.tfstate"
sas_token = "se=2023-11-21T09%3A51Z&sp=rwl&sip=94.233.160.130&spr=https&sv=2022-11-02&sr=c&sig=9G5tqn7RZxagUgnvpenBGm%2BNQVipRYOFxjz6l6mrvtg%3D"

检查新字符串以替换行:sas_token

minime@yd-202203101028:~/terraform  echo ${TEST_STRING}
se=1998-11-21T08%3A49Z&sp=rwl&sip=193.253.170.130&spr=https&sv=2022-11-02&sr=c&sig=DE7xk1ggg%3D

运行:sed

minime@yd-202203101028:~/terraform  sed 's/\(^sas_token =\)\(\s*.*$\)/\1 "'${TEST_STRING//&/\\&}'"/' .ENV/app-backend.tfvars
storage_account_name = "stoftfstate"
container_name = "tfstate-ct"
key = "terraform.tfstate"
sas_token = "se=1998-11-21T08%3A49Z&sp=rwl&sip=193.253.170.130&spr=https&sv=2022-11-02&sr=c&sig=DE7xk1ggg%3D"%                                                                                                           

看?HAD变了sas_token value

我缺少什么(或)?bashsh

bash sed sh zsh

评论

2赞 user1934428 11/14/2023
$NEW_TOKEN在任何引用之外。在 bash 中,首先扩展变量,然后拆分结果。在 zsh 中,首先对整个命令进行分词,然后对参数进行扩展。你不能指望 bash 和 zsh 的行为相同。首先确定您正在使用哪种语言,然后编写程序。编写程序并将其作为两种不同的语言运行是没有意义的。
3赞 user1934428 11/14/2023
所以你的问题归结为为什么我的 sed 命令不能按预期工作?在这种情况下,您发布了太多不相关的代码。你可以把你的示例简化为一个简单的例子,展示你得到的输出,并描述你期望的输出。TEST_STRING=.... ; echo "your token line" | sed YOUR_SED_PARAMETERS
3赞 user1934428 11/14/2023
在首字母之前有一个左单引号。它的结束语就在 .顺便说一句,幸运的是变量不在单引号之间;如果是这样,它就不会被扩展。s/$
1赞 shellter 11/14/2023
我想说,试试(-;请注意包装器字符前后的额外 dbl-quotes。 祝你好运。...."'"${NEW_TOKEN//&/\\&}"'"....${var}
1赞 Charles Duffy 11/15/2023
点头,这也是一个不可移植的扩展。-i

答:

2赞 Ed Morton 11/14/2023 #1

您看到的具体问题是由于您的报价造成的。您需要(在下面添加周围空格和带引号的字符串以强调):

sed 's/foo/"'  "$bar"  '"/'
    '<----->'  "<-->"  '<>'

而不是:

sed 's/foo/"'   $bar   '"/'
    '<----->'          '<>'

由于后者没有加引号,因此暴露在 shell 中进行解释,然后由您运行的 shell 以及其中设置的任何环境变量来决定如何处理它。$bar

您已经发现了一个需要转义的另一个问题,但需要转义的不仅仅是 s,其他字符甚至只是在反向引用(、等)中,请参阅是否有可能使用 sed 可靠地转义正则表达式元字符&&\/

根本问题是 sed 不理解文字字符串,但 awk 只是使用 awk 而不是尝试转义所有元字符来使 sed 的行为就像文字一样:

$ new='se=1998-11-21T08%3A49Z&sp=rwl&sip=193.253.170.130&spr=https&sv=2022-11-02&sr=c&sig=DE7xk1ggg%3D' \
    awk '$1=="sas_token" { $3="\"" ENVIRON["new"] "\"" } 1' file
storage_account_name = "stoftfstate"
container_name = "tfstate-ct"
key = "terraform.tfstate"
sas_token = "se=1998-11-21T08%3A49Z&sp=rwl&sip=193.253.170.130&spr=https&sv=2022-11-02&sr=c&sig=DE7xk1ggg%3D"

$ new='~!@#$%^&*()_+=-`|}{[]\":'\'';?></.,' \
    awk '$1=="sas_token" { $3="\"" ENVIRON["new"] "\"" } 1' file
storage_account_name = "stoftfstate"
container_name = "tfstate-ct"
key = "terraform.tfstate"
sas_token = "~!@#$%^&*()_+=-`|}{[]\":';?></.,"

请参阅如何在 awk 脚本中使用 shell 变量?,了解为什么我要在与 awk 脚本相同的行上指定 shell 变量值并使用(加上替代项)。ENVIRON[]

-1赞 aazizzailani 11/15/2023 #2

看起来这个问题与命令如何处理用特殊字符替换字符串有关,特别是 et 符号 (&)。在 Bash 中,参数扩展和转义的行为可能会有所不同,这可能是问题的原因。sed

若要解决此问题,可以尝试对命令使用以下替代语法:sed

sed -i 's/\(^sas_token =\)\(\s*.*$\)/\1 "'"$TEST_STRING"'" /' .ENV/app-backend.tfvars

此修改可确保变量正确展开并包含在命令中。它使用双引号来允许在用单引号引号引出的表达式中扩展参数。TEST_STRINGsedsed

以下是更新的命令:

sed -i 's/\(^sas_token =\)\(\s*.*$\)/\1 "'"${TEST_STRING//&/\\&}"'" /' .ENV/app- backend.tfvars

这应该在 Bash 中起作用并保持所需的行为。

评论

0赞 Ed Morton 11/15/2023
尝试使用 或 .顺便说一句,不是我的反对票。TEST_STRING='foo/bar'TEST_STRING='foo\1bar'
0赞 Charles Duffy 11/15/2023
反对开头的段落:在 sed 启动之前,将未加引号的字符串拆分为不同的参数发生在 shell 中。这不可能是 sed 命令如何做任何事情的问题(这就是为什么问题是特定于 shell 的,正如 OP 在问题中指出的那样)。您在建议的替代方案中所做的引用更改是 shell 引用而不是 sed 引用 -- 大多数引用甚至在开始之前就被 shell 删除了。sed