英文:
eval variable fails to execute sed command
问题
抱歉,我只会翻译文本,不会回答关于代码的问题。以下是您要翻译的内容:
"I wrote a bash script to remove an entry from crontab. I want to execute this command on remote servers and check if the command executed successfully. I'm trying this with the following script:
DELETEENTRY='sed -i '"/# Restarts HP OMO Agent Weekly/d"'' /var/spool/cron/root && echo "ok" || echo "error"'
DELETERESULT=$(ssh -q $HOST-- eval $DELETEENTRY)
if [ "$DELETERESULT" = "ok" ]
then
...........
fi
If I run the sed command as is, the command runs fine. If I use it with eval, I get the following error:
sed: -e expression #1, char 2: unterminated address regex
I tried multiple quotes of the /# Restarts HP OMO Agent Weekly/d String, but always ending up getting the same error.
Question
How can I use eval to successfully run the DELETENTRY variable?"
英文:
I wrote a bash script to remove an entry from crontab. I want to execute this command on remote servers and check if the command executed successfully. I'm trying this with the following script:
DELETEENTRY='sed -i '"'"'/# Restarts HP OMO Agent Weekly/d'"'"' /var/spool/cron/root && echo "ok" || echo "error"'
DELETERESULT=$(ssh -q $HOST-- eval $DELETEENTRY)
if [ "$DELETERESULT" = "ok" ]
then
...........
fi
If I run the sed command as is, the command runs fine. If I use it with eval, I get the following error:
sed: -e expression #1, char 2: unterminated address regex
I tried multiple quotes of the /# Restarts HP OMO Agent Weekly/d String, but always ending up getting the same error.
Question
How can I use eval to successfully run the DELETENTRY variable?
答案1
得分: 2
这部分代码设置了shell变量DELETEENTRY
的内容:
sed -i '/# Restarts HP OMO Agent Weekly/d' /var/spool/cron/root && echo "ok" || echo "error"
在执行以下命令时:
ssh -q $HOST-- eval $DELETEENTRY
shell会对参数进行扩展,得到:
ssh -q a.host.name-- eval sed -i '/# Restarts HP OMO Agent Weekly/d' /var/spool/cron/root && echo "ok" || echo "error"
请注意,--
之前似乎缺少一个空格,但我会假设这是问题中的一个拼写错误,否则将会有一个不同的问题。
这个命令将会受到单词分割的影响(嵌套的'
被视为普通字符),但在远程端,ssh
将会将命令及其所有参数重新连接在一起以作为输入传递给shell。
然而,当该shell处理生成的命令时,它会执行自己的引号去除操作,这里会导致你内部的单引号被消耗掉。
因此,最终由eval
执行的命令是:
sed -i /# Restarts HP OMO Agent Weekly/d /var/spool/cron/root
这个命令被分成了小块,从而导致错误(第一个sed
表达式只是/#
)。
我不清楚你在这里为什么要使用eval
。在最好的情况下,正确使用它也是有技巧的,而且在这种情况下,ssh
已经实现了与eval
基本相同的功能 —— 读取一个或多个参数,将它们形成一个字符串,并将其作为shell输入处理。请注意,这意味着以这种方式使用ssh
会让你面临使用eval
时的所有问题。
此外,将命令存储在变量中通常是个坏主意,但如果你一定要这样做,那么你应该使用数组而不是字符串,以便控制单词分割。也许可以像这样做:
DELETEENTRY=(
sed -i '/# Restarts HP OMO Agent Weekly/d' /var/spool/cron/root
'&&' echo ok
'||' echo error
)
DELETERESULT=$(ssh -q "$HOST" -- "${DELETEENTRY[@]}")
# ...
对数组的引号扩展形式("${DELETEENTRY[@]}"
)将每个元素扩展为单独的单词,这是在从变量扩展命令时通常会做的。然而,在这种特定情况下,远程ssh将把所有内容连接成一个字符串,所以如果你使用另一种数组扩展形式("${DELETEENTRY[*]}"
)也不会有任何区别。
请注意,eval
已经被移除了。正如前面讨论过的,这里不需要它。
存储在数组中的sed
表达式的单引号(看起来更容易阅读)会传递到远程shell中。在那里,它们会在构建和运行的shell命令中起到它们通常的作用。
还有其他一些替代方案和可能的改进,一些在另一个回答中有讨论。
英文:
This ...
> DELETEENTRY='sed -i '"'"'/# Restarts HP OMO Agent Weekly/d'"'"' /var/spool/cron/root && echo "ok" || echo "error"'
... sets the contents of shell variable DELETEENTRY
to:
sed -i '/# Restarts HP OMO Agent Weekly/d' /var/spool/cron/root && echo "ok" || echo "error"
When you then perform an
> ssh -q $HOST-- eval $DELETEENTRY
-
the shell applies parameter expansion to get
ssh -q a.host.name-- eval sed -i '/# Restarts HP OMO Agent Weekly/d' /var/spool/cron/root && echo "ok" || echo "error"
You appear to be missing a space before the
--
, but I'm going to assume that that's a typo in the question, because you would otherwise have a different issue. -
That command will be subject to word splitting (the embedded
'
are treated as ordinary characters for this purpose), but that's not actually a problem here, because on the remote side,ssh
will join the command and all its arguments back together with spaces to use as input to the shell. -
However, when that shell processes the resulting command, it will perform its own round of quote removal on it, and here your inner single quotes get consumed.
-
The command that is ultimately executed by
eval
is thereforesed -i /# Restarts HP OMO Agent Weekly/d /var/spool/cron/root
, which gets chopped into tiny little pieces by word splitting. Hence the error (the first
sed
expression is just/#
).
It's not clear to me why you're using eval
here. It's tricky to use correctly at the best of times, and in this case, ssh
already does substantially the same job that eval
does -- read one or more arguments, form them into a string, and process them as shell input. Note also that this means that using ssh
in this way exposes you to all the problems that using eval
does.
Additionally, It's usually bad news to store commands in variables, but if you must do so then you should use an array instead of a string so as to take control over word splitting. Maybe something like this, then:
DELETEENTRY=(
sed -i "'/# Restarts HP OMO Agent Weekly/d'" /var/spool/cron/root
'&&' echo ok
'||' echo error
)
DELETERESULT=$(ssh -q "$HOST" -- "${DELETEENTRY[@]}")
# ...
That form of quoted expansion of an array ("${DELETEENTRY[@]}"
) expands each element to a separate word, which is what one would ordinarily do when expanding a command from a variable. In this particular case, though, the remote ssh is just going to join it all together into a string anyway, so it wouldn't make a difference if you used the other flavor of array expansion ("${DELETEENTRY[*]}"
).
Note that the eval
has been removed. As already discussed, it is not needed here.
The single quotes around the sed
expression are stored in the array (and look how much easier that is to read) and conveyed to the remote shell. There, they serve their usual purpose in the shell command that is constructed and run.
There are other alternatives and possible improvements, too, some of which are discussed in another answer.
答案2
得分: 0
我建议使用declare -f
序列化该函数,然后在另一端加载并运行它。这样,您可以使用shellcheck正确检查您的脚本。
不需要使用echo
输出任何内容,退出状态会通过ssh传输。
func() {
sed -i '/# Restarts HP OMO Agent Weekly/d' /var/spool/cron/root
}
if ssh -q "$HOST" "$(declare -f func); func"; then
......
fi
因为这是一个命令,您可以使用printf %q
对字符串进行引用,就像您已经发现的那样,手动引用它很困难。
ssh "$HOST" "$(printf "%q " sed -i '/# Restarts HP OMO Agent Weekly/d' /var/spool/cron/root)"
此外,您可以考虑使用Ansible。
英文:
I recommend serializing the function with declare -f
and then load it on the other side and run. That way you can properly check your scripts with shellcheck.
No need to echo
anything, exit statuses are transferred over ssh.
func() {
sed -i '/# Restarts HP OMO Agent Weekly/d' /var/spool/cron/root
}
if ssh -q "$HOST" "$(declare -f func); func"; then
.........
fi
Because this is one command, you can quote the string for the other side using printf %q
. As you have found out, quoting it yourself is hard.
ssh "$HOST" "$(printf "%q " sed -i '/# Restarts HP OMO Agent Weekly/d' /var/spool/cron/root)"
Also, you may consider ansible.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论