替换变量中字符的最短方法

rub*_*o77 29 bash string variable

有很多方法可以替换变量中的字符。

tr到目前为止,我发现的最短方法是:

OUTPUT=a\'b\"c\`d_123and_a_lot_more
OUTPUT=$(echo "$OUTPUT"|tr -d "'\`\"")
echo $OUTPUT
Run Code Online (Sandbox Code Playgroud)

有没有更快的方法?对于像',"和 ` 本身这样的引号,这种引用安全吗?

ter*_*don 30

让我们来看看。我能想到的最短时间是对您的tr解决方案的调整:

OUTPUT="$(tr -d "\"\`'" <<<$OUTPUT)"
Run Code Online (Sandbox Code Playgroud)

其他替代方案包括已经提到的变量替换,它可能比目前显示的要短:

OUTPUT="${OUTPUT//[\'\"\`]}"
Run Code Online (Sandbox Code Playgroud)

sed当然,虽然这是根据字符长:

OUTPUT="$(sed s/[\'\"\`]//g <<<$OUTPUT)"
Run Code Online (Sandbox Code Playgroud)

我不确定你的意思是长度最短还是时间最短。就长度而言,在删除这些特定字符时,这两个尽可能短(或者我可以得到它)。那么,哪个最快?我通过将OUTPUT变量设置为您在示例中拥有的变量进行了测试,但重复了几十次:

$ echo ${#OUTPUT} 
4900

$ time tr -d "\"\`'" <<<$OUTPUT
real    0m0.002s
user    0m0.004s
sys     0m0.000s
$ time sed s/[\'\"\`]//g <<<$OUTPUT
real    0m0.005s
user    0m0.000s
sys     0m0.000s
$ time echo ${OUTPUT//[\'\"\`]}
real    0m0.027s
user    0m0.028s
sys     0m0.000s
Run Code Online (Sandbox Code Playgroud)

如您所见,tr显然是最快的,紧随其后的是sed。此外,似乎使用echo实际上比使用略快<<<

$ for i in {1..10}; do 
    ( time echo $OUTPUT | tr -d "\"\`'" > /dev/null ) 2>&1
done | grep -oP 'real.*m\K[\d.]+' | awk '{k+=$1;} END{print k/NR}'; 
0.0025
$ for i in {1..10}; do 
    ( time tr -d "\"\`'" <<<$OUTPUT > /dev/null ) 2>&1 
  done | grep -oP 'real.*m\K[\d.]+' | awk '{k+=$1;} END{print k/NR}'; 
0.0029
Run Code Online (Sandbox Code Playgroud)

由于差异很小,我对两者中的每一个都进行了 10 次上述测试,结果证明最快的确实是您必须开始的测试:

echo $OUTPUT | tr -d "\"\`'" 
Run Code Online (Sandbox Code Playgroud)

但是,当您考虑分配给变量的开销时,这会发生变化,这里, usingtr比简单替换稍慢:

$ for i in {1..10}; do
    ( time OUTPUT=${OUTPUT//[\'\"\`]} ) 2>&1
  done | grep -oP 'real.*m\K[\d.]+' | awk '{k+=$1;} END{print k/NR}'; 
0.0032

$ for i in {1..10}; do
    ( time OUTPUT=$(echo $OUTPUT | tr -d "\"\`'")) 2>&1
  done | grep -oP 'real.*m\K[\d.]+' | awk '{k+=$1;} END{print k/NR}'; 
0.0044
Run Code Online (Sandbox Code Playgroud)

因此,总而言之,当您只想查看结果时,请使用tr但如果您想重新分配给变量,使用 shell 的字符串操作功能会更快,因为它们避免了运行单独子 shell 的开销。

  • 由于 OP 有兴趣将修改后的值设置回“OUTPUT”,因此您必须考虑“tr”和“sed”解决方案中涉及的命令替换子 shell 开销 (4认同)
  • 涉及命令替换的方法的缺点是稍微修改了字符串。(您可以避免它,但代价是使命令变得更加复杂。)特别是,命令替换会删除尾随的换行符。 (2认同)

cha*_*aos 22

您可以使用变量替换

$ OUTPUT=a\'b\"c\`d
$ echo "$OUTPUT"
a'b"c`d
Run Code Online (Sandbox Code Playgroud)

使用该语法:${parameter//pattern/string}用字符串替换所有出现的模式。

$ OUTPUT=a\'b\"c\`d
$ echo "$OUTPUT"
a'b"c`d
Run Code Online (Sandbox Code Playgroud)


gen*_*a2x 13

在 bash 或 zsh 中,它是:

OUTPUT="${OUTPUT//[\`\"\']/}"
Run Code Online (Sandbox Code Playgroud)

请注意,${VAR//PATTERN/}删除模式的所有实例。更多信息bash 参数扩展

此解决方案对于短字符串应该是最快的,因为它不涉及运行任何外部程序。然而,对于很长的字符串,情况正好相反——最好使用专用工具进行文本操作,例如:

$ OUTPUT="$(cat /usr/src/linux/.config)"

$ time (echo $OUTPUT | OUTPUT="${OUTPUT//set/abc}")
real    0m1.766s
user    0m1.681s
sys     0m0.002s

$ time (echo $OUTPUT | sed s/set/abc/g >/dev/null)
real    0m0.094s
user    0m0.078s
sys     0m0.006s
Run Code Online (Sandbox Code Playgroud)


mik*_*erv 7

如果在偶然情况下,您只是尝试处理引号以重新使用 shell,那么您可以在删除它们的情况下执行此操作,而且它也非常简单:

aq() { sh -c 'for a do
       alias "$((i=$i+1))=$a"
       done; alias' -- "$@"
}
Run Code Online (Sandbox Code Playgroud)

该函数 shell 引用您传递给它的任何 arg 数组,并根据可迭代参数递增其输出。

这是一些参数:

aq \
"here's an
ugly one" \
"this one is \$PATHpretty bad, too" \
'this one```****```; totally sucks'
Run Code Online (Sandbox Code Playgroud)

输出

1='here'"'"'s an
ugly one'
2='this one is $PATHpretty bad, too'
3='this one```****```; totally sucks'
Run Code Online (Sandbox Code Playgroud)

该输出dash通常安全引用单引号输出,如'"'"'. bash会做'\''

在任何带有$IFS和 的POSIX shell 中,用另一个单个字节替换单个、非空白、非空字节的选择可能是最快的$*

set -f; IFS=\"\'\`; set -- $var; printf %s "$*"
Run Code Online (Sandbox Code Playgroud)

输出

"some ""crazy """"""""string ""here
Run Code Online (Sandbox Code Playgroud)

我只是printf这样你可以看到它,但当然,如果我这样做了:

var="$*"
Run Code Online (Sandbox Code Playgroud)

...而不是printfcommand$var的值将是您在那里的输出中看到的值。

当我set -f指示 shell不要使用 glob - 以防字符串包含可以被解释为 glob 模式的字符。我这样做是因为 shell 解析器对变量执行字段拆分扩展了 glob 模式。globbing 可以像set +f. 一般来说 - 在脚本中 - 我发现将我的爆炸设置为:

#!/usr/bin/sh -f
Run Code Online (Sandbox Code Playgroud)

然后明确启用通配符set +f在任何行我可能想它。

字段拆分基于 中的字符进行$IFS

有两种$IFS值 -$IFS空白和$IFS非空白。$IFS空格(空格、制表、换行符)分隔的字段被指定为按顺序删除到单个字段(如果它们不在其他内容之前,则根本没有) - 所以......

IFS=\ ; var='      '; printf '<%s>' $var
<>
Run Code Online (Sandbox Code Playgroud)

但所有其他人都被指定为每次出现评估一个字段- 它们不会被截断。

IFS=/; var='/////'; printf '<%s>' $var
<><><><><>
Run Code Online (Sandbox Code Playgroud)

默认情况下,所有变量扩展都是$IFS分隔数据数组 - 它们根据$IFS. 当您"引用一个时,您会覆盖该数组属性并将其评估为单个字符串。

所以当我做...

IFS=\"\'\`; set -- $var
Run Code Online (Sandbox Code Playgroud)

我将 shell 的参数数组设置$IFS为由$var的扩展生成的许多分隔字段。当扩大其组成值字符包含在$IFS丢失-它们只是场分离了-他们\0NUL

"$*"- 与其他双引号变量扩展一样 - 也覆盖$IFS. 但是,除此之外,它替换的第一个字节$IFS 为每个分隔的领域"$@"。所以因为"所有后续分隔符中第一个$IFS 变成""$*". "不必是$IFS当你把它分解,无论是。您可以将$IFS after set -- $args完全更改为另一个值,然后其新的第一个字节将显示在"$*". 更重要的是,您可以完全删除它们的所有痕迹:

set -- $var; IFS=; printf %s "$*"
Run Code Online (Sandbox Code Playgroud)

输出

some crazy string here
Run Code Online (Sandbox Code Playgroud)