【问题标题】:regex \s not honored in sed via ssh正则表达式 \s 在 sed 中不通过 ssh
【发布时间】:2018-03-09 15:22:29
【问题描述】:

我正在尝试通过 ssh 在 bash 脚本中编辑远程文件:

文件如下所示:

{
  "lastqueueid" : "7865904306",
  "jobqueuetimestamp" : "1506489874000",
  "mincleanstartsegment" : "7516101",
  "lastposition" : "519949359362951172",
  "maxsegment" : "7566259"
}

我正在尝试使用 sed 将 "lastqueueid"7865904306 更改为 $LATEST_QUEUE。所以我尝试了......

ssh user@host sed \
    "s@"lastqueueid"\s\:\s\"[0-9]+\"@"lastqueueid"\s\:\s"$LATEST_QUEUE"@g" \
    /file.json

sed 不会抛出错误,但也不会进行替换。似乎它没有将\s 视为空格。如果我删除 \s 并添加一个文字空间,我会得到可怕的 unterminated `s' command 错误。

任何帮助将不胜感激。谢谢!

【问题讨论】:

  • 我很难解密你的命令。也许您想更清楚地描述您的问题,以及“不进行替换”是什么意思。
  • @HuStmpHrrr 问题很明确。 “不进行替换”意味着没有成功“将 lastqueueid 从 7865904306 更改为 $LATEST_QUEUE。
  • 如果那是您运行的确切命令,那么您的引号是错误的。
  • @OP $LATEST_QUEUE 设置了吗?如果是,在哪里你在哪里设置的?
  • shell 正在解释 \s,因为它在 see 获得命令之前用双引号引起来。它们正在变成文字 s。在 shell 中设置 -x 以了解更多关于它在做什么可能会有所帮助。

标签: linux bash ssh sed


【解决方案1】:

写这么复杂的命令,让 ssh 满意,并不容易:

[STEP 101] # ssh 127.0.0.1 cat tmp/file.json
{
  "lastqueueid" : "7865904306",
  "jobqueuetimestamp" : "1506489874000",
  "mincleanstartsegment" : "7516101",
  "lastposition" : "519949359362951172",
  "maxsegment" : "7566259"
}
[STEP 102] # LATEST_QUEUE=1111111111
[STEP 103] # ssh 127.0.0.1 sed -Ee \''s@^(\s*"lastqueueid"\s*:\s*)"[0-9]+"@\1"'\'$LATEST_QUEUE\''"@'\' tmp/file.json
{
  "lastqueueid" : "1111111111",
  "jobqueuetimestamp" : "1506489874000",
  "mincleanstartsegment" : "7516101",
  "lastposition" : "519949359362951172",
  "maxsegment" : "7566259"
}
[STEP 104] #

对于(或?),请按照以下步骤生成可以安全地传递给ssh的命令字符串:

  1. 创建一个命令并验证它可以在本地系统上正确运行。例如:

    [STEP 201] # echo 11 22 33 | sed -e 's/\s/_/g'
    11_22_33
    [STEP 202] #
    
  2. 将命令保存到文件中:

    [STEP 202] # cat cmdfile
    echo 11 22 33 | sed -e 's/\s/_/g'
    [STEP 203] #
    
  3. 使用printf %q生成要传递给ssh的命令字符串:

    [STEP 203] # sshcmd=$( printf '%q' "$(< cmdfile)" )  # double quoted
    [STEP 204] # printf '%s\n' "$sshcmd"                 # dobule quoted
    echo\ 11\ 22\ 33\ \|\ sed\ -e\ \'s/\\s/_/g\'
    [STEP 205] #
    
  4. 使用evalbash -c 将cmdstring 传递给ssh:

    [STEP 205] # ssh localhost eval "$sshcmd"     # double quoted
    11_22_33
    [STEP 206] # ssh localhost bash -c "$sshcmd"  # double quoted
    11_22_33
    [STEP 207] #
    

Bash 4.4+ 中,我们还可以使用%{var@Q} 语法,它会产生更短且更易于阅读的输出:

[STEP 300] # echo $BASH_VERSION
4.4.12(2)-release
[STEP 301] # sshcmd=$(<cmdfile)
[STEP 302] # printf '%s\n' "$sshcmd"
echo 11 22 33 | sed -e 's/\s/_/g'
[STEP 303] # sshcmd=${sshcmd@Q}
[STEP 304] # printf '%s\n' "$sshcmd"
'echo 11 22 33 | sed -e '\''s/\s/_/g'\'''
[STEP 305] # ssh 127.0.0.1 eval "$sshcmd"
11_22_33
[STEP 306] # ssh 127.0.0.1 bash -c "$sshcmd"
11_22_33
[STEP 307] #

【讨论】:

  • 谢谢,这成功了!你能帮我理解它为什么起作用吗?我得到了大部分内容,但我不确定为什么必须在开头和结尾添加 \' ,也不知道为什么必须在开头使用 ^( 。另外,不做 / 1 只是告诉 sed 只进行 1 次替换?感谢您的帮助!
  • ^ 是 bourne shell 特有的....是管道字符 | 的旧版本,所以必须转义。
【解决方案2】:

这里至少存在三个问题:

  1. sed 的许多版本不理解 \s —— 那是 perl 的事情。您将在 unix/linux 系统上遇到三种主要的正则表达式语法变体:basic (BRE), extended (ERE), and perl-compatible (PCRE)sed 默认使用 BRE,但可以通过 -r-E 选项告诉您使用 ERE 语法(取决于您拥有的 sed 的版本)。 BRE 和 ERE 语法中 \s 的等价物是 [[:space:]]

    另一个问题是 BRE 语法无法识别 +(如 [0-9]+)。只需使用[0-9][0-9]*。哦,你不需要在任何 RE 语法中转义 :

  2. 引号不会嵌套。当您使用"s@"lastqueueid"\s... 时,shell 将"s@" 视为带引号的字符串,lastqueueid 视为不带引号的字符串,"\s... 视为另一个带引号的字符串的开头,等等。您需要将内引号视为字符串而不是它(部分)周围的分隔符(即你需要文字,而不是句法,引号)。一种方法是转义应该是文字的引号("s@\"lastqueueid\"\s...),另一种方法是对外部引号使用不同类型的引号(单引号和双引号)('s@"lastqueueid"\s...' - 但$variable 不会' t 用单引号展开)。

    但还有一个复杂的问题,因为...

  3. 由于这个命令是通过ssh 传递的,它会经历shell 解析和引用/转义解释和删除的整个过程两次,一次在本地计算机上,然后再次由shell在远程计算机上。这意味着您需要在引号周围加上引号(第一个由本地 shell 解释和删除,第二个由远程 shell 解释和删除,第三个被视为要匹配的模式的一部分)。这变得一团糟。

    顺便说一句,这第三个问题是为什么您在使用实际空格时会收到“未终止的's'命令”错误——当它到达远程 shell 时,命令看起来像这样:

    sed s@lastqueueid : '[0-9]+@lastqueueid' : 123456789@g /file.json
    

    ...所以sed 尝试将s@lastqueueid 作为命令执行(其余的都是文件名),但无法解析它。

那么,该怎么办?在 m 看来,最简单的引用策略是在转义的双引号周围使用双引号围绕单引号。像这样的:

"'s@\"lastqueueid\" : \"[0-9][0-9]*\"@\"lastqueueid\" : \"$LATEST_QUEUE\"@g'"

在被本地 shell 解析后,ssh 接收(并传递给远程 shell)如下内容:

's@"lastqueueid" : "[0-9][0-9]*"@"lastqueueid" : "123456789"@g'

...远程外壳删除单引号,sed 得到这个:

s@"lastqueueid" : "[0-9][0-9]*"@"lastqueueid" : "123456789"@g

...这就是你想要的。

嗯,也许吧。我看到了其他几个可能的问题。首先,文件实际上是 /file.json(即位于远程文件系统的根目录)吗?如果没有,请修复路径。其次,您是否尝试用编辑后的版本替换远程文件?默认情况下,sed 只是打印出编辑后的版本,而不是保存它。要进行就地编辑,请使用-i.bak(其中“.bak”是保存预编辑备份的文件扩展名)。

【讨论】:

  • 谢谢!这很有效,实际上对我来说是迄今为止提供的解决方案中最有意义的。
  • @EdMorton 感谢您指出这一点;我已经相应地修改了我的答案。我确实测试了 GNU sed,但显然我测试的 linux 系统有一个非常旧的版本(4.1.2),从那时起,这个功能就被添加了。
猜你喜欢
  • 2018-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-18
  • 1970-01-01
  • 1970-01-01
  • 2010-09-19
  • 1970-01-01
相关资源
最近更新 更多