我正在制作一个在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
我可以使用它可能更好用?或者是否有一些引号和转义的组合可以使这个工作?我无法弄清楚。
如果有人需要更多细节,我很乐意提供;我只是不知道什么是相关的。
答案 0 :(得分:6)
TL; DR:您可以在函数中使用eval
并在您传递的命令中包含正确的内部引号,这对于复杂命令来说很麻烦,并且可能会生成使用您的代码的代码功能很难阅读,但它有一些优点。或者,您可以将命令作为多个参数传递给shell函数,在初始颜色参数之后,在存储或以其他方式使用之后移出颜色参数,然后以"$@"
运行命令。请参阅下面的完整详细信息(这里也可能没有涉及的方法;也许其他人会写下关于它们的答案。)
${CMD}
中的${CMD} || exit 1
未quoted,word splitting和globbing被执行。这只能在最简单的情况下实现你真正想要的。每个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
有多种方法可以解决这个问题。我将展示两个。
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
。
通常,当您在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
。