sed问题:"糟糕的模式"

时间:2018-01-06 16:55:38

标签: android shell sed

我正在制作一个在Android上运行的脚本(因此这是一个奇怪的shebang路径)。它涉及使用sed注释掉指定文件中的某些代码块。目前,我试图将整个sed命令传递给函数,但我在这方面遇到了很多麻烦。

这是剧本:

#!/system/bin/sh

REM_RCTD=$1
REM_CCMD=$2
REM_TRITON=$3
DEVICE_CODE=$4

COLOR_GRN_PRE="<font color='#00ff00'>"
COLOR_YEL_PRE="<font color='#ffff00'>"
COLOR_POS="</font>"

YELLOW=0
GREEN=1

echoAndExec() {
    CMD="$2"

    if [ "$1" = ${YELLOW} ]; then
        echo "$COLOR_YEL_PRE $CMD $COLOR_POS"
    elif [ "$1" = ${GREEN} ]; then
        echo "$COLOR_GRN_PRE $CMD $COLOR_POS"
    fi

    ${CMD} || exit 1
}

if [ "$REM_RCTD" = "true" ]
then

    CMD="sed -ir -e \_^# LG RCT(Rooting Check Tool)$_,/^$/{/^(#\|$)/!s/^/#/} init.lge.rc"
    echoAndExec ${YELLOW} "${CMD}"
fi

if [ "$REM_CCMD" = "true" ]
then

    CMD="sed -ir -e \_^service ccmd /system/bin/ccmd$_,/^$/{/^(#\|$)/!s/^/#/} init.lge.rc"
    echoAndExec ${YELLOW} "${CMD}"
fi

if [ "$REM_TRITON" = "true" ]
then

    CMD="sed -ir -e /# triton service/,\_chmod 644 /sys/devices/system/cpu/triton/enable_s/^/# / init.${DEVICE_CODE}.power.rc"
    echoAndExec ${YELLOW} "${CMD}"
fi

发送到echoAndExec()函数的每个命令都可以正常工作,sed除外,它返回

sed: bad pattern '\_^#@1 (\)

对于第三个sed命令,它与/存在相同的问题。

我尝试过一系列引号组合,使用和不使用变量,模式开头的语法不同(/#,_),但我真的输了。从我的脚本中可以明显看出,我几乎是shell脚本的初学者。

是否可能使用POSIX替代sed我可以使用它可能更好用?或者是否有一些引号和转义的组合可以使这个工作?我无法弄清楚。

如果有人需要更多细节,我很乐意提供;我只是不知道什么是相关的。

1 个答案:

答案 0 :(得分:6)

TL; DR:您可以在函数中使用eval并在您传递的命令中包含正确的内部引号,这对于复杂命令来说很麻烦,并且可能会生成使用您的代码的代码功能很难阅读,但它有一些优点。或者,您可以将命令作为多个参数传递给shell函数,在初始颜色参数之后,在存储或以其他方式使用之后移出颜色参数,然后以"$@"运行命令。请参阅下面的完整详细信息(这里也可能没有涉及的方法;也许其他人会写下关于它们的答案。)

问题

${CMD}中的${CMD} || exit 1quotedword splittingglobbing被执行。这只能在最简单的情况下实现你真正想要的。每个sed命令都包含您打算作为单个参数传递给sed但包含空格的文本。这些空格使它分成多个单词,每个单词作为单独的命令行参数传递给sed

考虑您要运行的命令的简化情况:

printf '%s\n' 'foo bar' 'baz quux'

当您使用%s\n作为其第一个参数运行printf时,它会在一行上自行打印其后续参数。这是检查单词拆分和通配的效果的便捷方法。该特定命令的输出是:

foo bar
baz quux

如果将整个命令分配给CMD 而没有内部引号,则此特定命令的简单性会使问题立即显而易见:shell无法知道您的位置而且不打算让它进行分词。

$ CMD="printf %s\n foo bar baz quux"
$ $CMD
foo
bar
baz
quux

但这正是您使用每个 sed命令的情况。 sed命令中的某些空格用于分隔参数,而其他空格则不是,并且shell无法知道您想要的内容。

将内部引号嵌入CMD的值将解决问题。反正不是它本身。由于他们自己被引用,他们的特殊意义被压制。 Quote removal在这种情况下不会发生,因此它们只会保留在您放置它们的单词的边缘。此外,您打算引用它们的空格仍会导致分词:

$ CMD="printf %s\n 'foo bar' 'baz quux'"
$ $CMD
'foo
bar'
'baz
quux'

当然,您可以在双引号($CMD)中展开"$CMD" 。但这会阻止所有单词拆分,并尝试运行名称为整个命令的程序,空格和全部。那不是你想要的:

$ "$CMD"
printf %s\n 'foo bar' 'baz quux': command not found

有多种方法可以解决这个问题。我将展示两个。

方式1:您可以使用eval

使该命令运行命令

一种解决方案是在双引号中展开$CMD以防止分词,但不是运行,而是将其作为参数传递给eval shell内置。这导致shell解析CMD的内容,就像这些内容在脚本中实际显示为一行时一样。请注意,由于未引用CMD中存储的实际文本,因此您必须包含需要引用的所有的内部引号。在简单的情况下,您可以使用" "来获取内部引号或外部引号:

$ CMD='printf "%s\n" "foo bar" "baz quux"'
$ eval "$CMD"
foo bar
baz quux
$ CMD="printf '%s\n' 'foo bar' 'baz quux'"
$ eval "$CMD"
foo bar
baz quux

但是,对于包含$等字符的命令,如果在双引号内扩展可能会被特别处理 - 就像您使用sed所做的那样 - 通常需要使用单引号都。要实现这一点,您可以结束引用足够长的时间来编写单引号本身quoted with \,然后再次重新引用。也就是说,你可以写一个单引号&#34;内部&#34;单引号为'\''

$ CMD='printf '\''%s\n'\'' '\''foo bar'\'' '\''baz quux'\'''
$ eval "$CMD"
foo bar
baz quux

这种方法的主要缺点是很难正确引用命令将它们传递给shell函数,而且你(或其他人)通过检查来验证它们是否正确是非常困难的。但是,它的优点是函数具有一个可以像命令一样运行的字符串。通常这不重要,但在您的情况下,它可能很重要,因为您向用户显示命令是什么。如果您希望用户看到可以运行的命令,请逐字 - 也就是说,您希望显示正确引用用户,而不仅仅是正确运行命令 - 然后{{1基于方法可能是实现这一目标的最简单方法。

这是shell函数的修改版本。它采用开始标记中的eval属性值而不是数字作为第一个参数。我实际上建议你这样做,因为color$YELLOW不能分别只是$GREEN#ffff00。但是,无论您选择编写它,都应该演示如何在其中使用#00ff00

eval

正如您所看到的,我还replaced echo with printf,因为某些echo实现扩展了转义序列,您可能不想在此处转义。 (有些人也可以将你传递的第一个参数视为一个选项,但这不是问题,因为你的第一个参数以echoAndExec() { printf '<font color='\''%s'\''> %s </font>\n' "$1" "$2" eval "$2" || exit 1 } 开头,而不是<。)最后,我把变量设为小写。我建议您为shell variables使用小写名称,除非您打算将其导出为environment variables。对于环境变量和由shell专门处理的变量(例如,-)以大写字母命名是常见的,并且使用小写有助于避免冲突。不过,如果你愿意,你可以使用大写。

以下是您可以调用该功能的方法:

PS1

或者只是:

$ green='#00ff00'
$ cmd='printf '\''%s\n'\'' '\''foo bar'\'' '\''baz quux'\'''
$ echoAndExec "$green" "$cmd"
<font color='#00ff00'> printf '%s\n' 'foo bar' 'baz quux' </font>
foo bar
baz quux

当然,无论您是否先将命令分配给变量,您都不必每次都重新定义$ green='#00ff00' $ echoAndExec "$green" 'printf '\''%s\n'\'' '\''foo bar'\'' '\''baz quux'\''' <font color='#00ff00'> printf '%s\n' 'foo bar' 'baz quux' </font> foo bar baz quux

方式2:您可以将命令作为多个参数传递给函数

通常,当您在parameter expansion中执行double quotes时,word splitting会被完全取消。但是,green参数是特殊的。 @*都会一个接一个地包含 all positional parameters的文本,但是当用双引号展开时,它们的行为会有所不同。使用@,你得不到任何单词分裂 - 位置参数被连接起来,它们之间有单个空格(或者"$*"的第一个字符)。

相反,对于$IFS,在位置参数之间执行分词,但不在其中。这就是说每个位置参数都成为它自己的单词,但是包含空格的位置参数仍然会被赢得进一步分割(就像"$@"或{ {1}} 外部 $* $@)。

这提供了将命令传递给函数所需的功能,以允许您以可读方式编写命令的方式,并使函数正确运行。如果你想采用这种方法,可以在这里编写你的函数:

"

最初运行"将无法执行您想要的操作,因为它会在开头包含第一个位置参数。要删除第一个位置参数,同时将每个其他位置参数向下移动一个(或者您可能更愿意将其视为将它们移动一个),我使用shift内置。< / p>

该代码的可读性稍差,因为我避免在shell函数中引入任何变量。我这样做的原因是echoAndExec() { printf '<font color='\''%s'\''> ' "$1" shift printf '%s </font>\n' "$*" "$@" || exit 1 } "$@"内置函数是not actually required by POSIX,我不确定您的shell - 以及您可能需要运行此脚本的其他shell - 支持他们。没有它们,分配给shell函数中的变量也会导致它们被设置为调用者。对于您已经显示的特定脚本,这似乎不是一个问题,但我不知道您是否(或如何)最终扩展脚本或者您的变量名称是什么可能会在以后使用它。

与您的函数版本相比,上面显示的某些更改既不特定于展开declare的方法,也不特定于使用local的前一种方法。我在上一节中解释了这些变化。尽管你可以,但你不必用这种方式编写你的函数;以上代码的目的是作为一个例子。

重要的是要记住,必须以不同的方式调用此函数,而不是调用您最初编写的函数的方式(也有所不同)来自上面显示的基于"$@"的版本。不要先将命令存储在变量中。只需将命令的每个单词传递给shell函数:

eval

您会注意到,这更容易调用,因为您不必使用任何超出您直接运行命令的引用。实际上,您必须不使用此类额外引用。使用此功能并了解您所编写的内容并验证其是否正确非常容易。

但是,这确实有一个缺点,即打印使用其原始引号传入的命令不再是微不足道的。 shell调用函数时会删除这些引号。该功能无法访问它们。

您可能不关心这一点,但如果您这样做,那么您可以使shell函数在每个参数周围插入引号。它们不一定是最初使用的相同引号,如果参数本身包含单引号,它们甚至可能都不正确。但他们通常应该以合理的方式明确指出通过的论点:

eval

您将以相同的方式使用该功能。它看起来像这样:

$ green='#00ff00'
$ echoAndExec "$green" printf '%s\n' 'foo bar' 'baz quux'
<font color='#00ff00'> printf %s\n foo bar baz quux </font>
foo bar
baz quux

(如上所述,您不必每次都重新定义echoAndExec() { printf '<font color='\''%s'\''> ' "$1" shift printf \''%s'\'' ' "$@" printf '</font>\n' "$@" || exit 1 } 。我刚刚这样做,以便明确$ green='#00ff00' $ echoAndExec "$green" printf '%s\n' 'foo bar' 'baz quux' <font color='#00ff00'> 'printf' '%s\n' 'foo bar' 'baz quux' </font> foo bar baz quux 的含义我编写的shell函数的版本,因此很清楚如何轻松地测试它。)

虽然可以这样做,但我强调它显示的命令并不总是可以如图所示运行,因为它不能正确处理内部单引号。向用户显示的命令对人类来说非常好,但对于计算机来说并不是那么好。因此,虽然您可以使用此修改后的方法,但最好采用上面显示的基于green的方式, if ,您需要向用户显示带有原始的命令(或其他方式)正确的引用。

当然,也可以以更复杂的方式处理参数,例如,将green的内部事件正确转换为eval

进一步阅读