gpt4 book ai didi

bash - POSIX sh相当于Bash的printf%q

转载 作者:行者123 更新时间:2023-11-29 08:45:53 24 4
gpt4 key购买 nike

假设我有一个#!/bin/sh脚本,该脚本可以采用各种位置参数,其中一些参数可能包含空格,任一/两种引号等。我要迭代"$@",对于每个参数要么立即以某种方式处理它,要么将其保存为后来。在脚本的结尾,我想启动另一个过程(也许是exec),传入一些带有完整特殊字符的参数。

如果我不对参数进行任何处理,othercmd "$@"可以正常工作,但是我需要提取一些参数并对其进行一些处理。

如果我可以假设Bash,那么我可以使用printf %q计算引号的args版本,以后可以使用eval,但这在例如Ubuntu的Dash(/bin/sh)。

仅使用内置功能和POSIX定义的实用程序,是否可以用普通的Bourne shell脚本编写相当于printf %q的代码,例如可以复制到脚本中的函数?

例如,一个脚本尝试以相反的顺序对其参数进行ls:

#!/bin/sh
args=
for arg in "$@"
do
args="'$arg' $args"
done
eval "ls $args"

适用于许多情况:
$ ./handle goodbye "cruel world"
ls: cannot access cruel world: No such file or directory
ls: cannot access goodbye: No such file or directory

但是当使用 '时不是:
$ ./handle goodbye "cruel'st world"
./handle: 1: eval: Syntax error: Unterminated quoted string

并且以下工作正常,但依赖于Bash:
#!/bin/bash
args=
for arg in "$@"
do
printf -v argq '%q' "$arg"
args="$argq $args"
done
eval "ls $args"

最佳答案

这绝对是可行的。

杰西·格里克(Jesse Glick)所见的答案大约在那儿,但是它有几个错误,我还有其他一些选择供您考虑,因为这是我不止一次遇到的问题。

首先,您可能已经知道,echo是一个坏主意,如果目标是可移植性,则应该改用printf:如果POSIX收到的参数为“-n”,则“echo”在POSIX中具有未定义的行为,实际上回显的实现将-n视为特殊选项,而其他实现则将其视为打印的常规参数。这样就变成了:

esceval()
{
printf %s "$1" | sed "s/'/'\"'\"'/g"
}

另外,也可以通过使嵌入的单引号转义为:
'"'"'

..相反,您可以将它们变成:
'\''

我猜是..风格差异(尽管我从未测试过,但我认为性能差异可以忽略不计)。生成的sed字符串如下所示:
esceval()
{
printf %s "$1" | sed "s/'/'\\\\''/g"
}

(这是四个反斜杠,因为双引号将两个括起来,然后保留两个,然后sed吞下一个,然后只保留一个。个人而言,我发现这种方式更具可读性,因此在涉及以下示例的其余示例中将使用此方式它,但两者应该等效。)

但是,我们仍然有一个错误:命令替换将从命令输出中删除至少一个尾随换行符(但在许多Shell中为ALL)(不是所有空格,特别是换行符)。因此,除非您在参数末尾使用换行符,否则上述解决方案都可以使用。然后,您将丢失该/那些换行符。解决方法显然很简单:在引号/ esceval函数输出之前,在实际命令值之后添加另一个字符。顺便说一句,我们已经需要这样做了,因为我们需要用单引号开始和停止转义的参数。老实说,我不明白为什么一开始没有做到这一点。您有两种选择:
esceval()
{
printf '%s\n' "$1" | sed "s/'/'\\\\''/g; 1 s/^/'/; $ s/$/'/"
}

这将确保引出的参数已经完全转义,构建最终字符串时无需添加更多单引号。这可能是您获得单个可内联版本的最接近的内容。如果您对sed依赖没有关系,可以在这里停止。

如果您对sed依赖项不满意,但可以假设您的shell实际上是POSIX兼容的(这里仍然有一些,特别是Solaris 10及更低版本上的/ bin / sh,不会)能够执行下一个变体-但几乎所有您需要关心的shell都可以做到这一点):
esceval()
{
printf \'
UNESCAPED=$1
while :
do
case $UNESCAPED in
*\'*)
printf %s "${UNESCAPED%%\'*}""'\''"
UNESCAPED=${UNESCAPED#*\'}
;;
*)
printf %s "$UNESCAPED"
break
esac
done
printf \'
}

您可能会注意到这里的看似多余的引用:
printf %s "${UNESCAPED%%\'*}""'\''"

..这可以替换为:
printf %s "${UNESCAPED%%\'*}'\''"

我做前者的唯一原因是因为曾经有一个Bourne shell 在将变量替换为带引号的字符串时存在错误,其中变量周围的引号并没有完全在变量替换处开始和结束。因此,这是我的偏执携带习惯。在实践中,您可以执行后者,这不会有问题。

如果您不想在其余shell环境中破坏变量UNESCAPED,则可以将该函数的全部内容包装在子shell中,如下所示:
esceval()
{
(
printf \'
UNESCAPED=$1
while :
do
case $UNESCAPED in
*\'*)
printf %s "${UNESCAPED%%\'*}""'\''"
UNESCAPED=${UNESCAPED#*\'}
;;
*)
printf %s "$UNESCAPED"
break
esac
done
printf \'
)
}

“但是等等”,您说:“我想在一个命令中对多个参数执行此操作吗?如果我出于某种原因从命令行运行该输出,我希望该输出对于用户来说仍然看起来不错且清晰易读。”

不用担心,我已经覆盖了您:
esceval()
{
case $# in 0) return 0; esac
while :
do
printf "'"
printf %s "$1" | sed "s/'/'\\\\''/g"
shift
case $# in 0) break; esac
printf "' "
done
printf "'\n"
}

..或同一件事,但具有仅 shell 版本:
esceval()
{
case $# in 0) return 0; esac
(
while :
do
printf "'"
UNESCAPED=$1
while :
do
case $UNESCAPED in
*\'*)
printf %s "${UNESCAPED%%\'*}""'\''"
UNESCAPED=${UNESCAPED#*\'}
;;
*)
printf %s "$UNESCAPED"
break
esac
done
shift
case $# in 0) break; esac
printf "' "
done
printf "'\n"
)
}

在最后四个中,您可以折叠一些外部printf语句,并将其单引号向上滚动到另一个printf中-我将它们分开,因为当您可以看到分开的开始和结束单引号时,我认为这样做使逻辑更加清晰打印报表。

附言我还做了一个怪兽,这是一个polyfill,它将根据您的 shell 是否似乎能够支持必要的变量替换语法在前两个版本之间进行选择(尽管看起来很糟糕,因为仅 shell 版本必须是在评估字符串中以防止不兼容的shell在看到它们时不发怒): https://github.com/mentalisttraceur/esceval/blob/master/sh/esceval.sh

关于bash - POSIX sh相当于Bash的printf%q,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12162010/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com