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


18

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

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

OUTPUT=a\'b\"c\`d_123and_a_lot_more
OUTPUT=$(echo "$OUTPUT"|tr -d "'\`\"")
echo $OUTPUT

有没有更快的方法?而且这是引用安全像引号'"和'本身?


我认为您可以继续使用tr。BASH的PE很好,但是在这种情况下tr更快。例如,echo "$OUTPUT" | tr -dc '[[:alpha:]]' 因为您只想使用字母数字
Valentin Bajrami 2014年

2
由于您有兴趣理解引号,因此:始终引用变量!echo "$OUTPUT"。或更好:printf "%s\n" "$OUTPUT"。(什么时候会发生OUTPUT="-n"?)
musiphil 2014年

您可能还考虑浏览codegolf,尤其是bash技巧。
hoosierEE 2014年

Answers:


22

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

OUTPUT="$(tr -d "\"\`'" <<<$OUTPUT)"

其他选择包括已经提到的变量替换,它可能比到目前为止显示的要短:

OUTPUT="${OUTPUT//[\'\"\`]}"

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

OUTPUT="$(sed s/[\'\"\`]//g <<<$OUTPUT)"

我不确定您是指长度最短还是花费时间最短。就长度而言,在删除这些特定字符时,这两个长度要尽可能短(或者无论如何我都可以得到)。那么,哪个最快?我通过将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

如您所见,tr显然是最快的,其次是sed。另外,似乎using echo实际上比using快一点<<<

$ 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

由于差异很小,因此我对两个测试分别进行了10次测试,结果发现最快的测试确实是您必须开始的测试:

echo $OUTPUT | tr -d "\"\`'" 

但是,当您考虑分配给变量的开销时,这种情况会改变,这里使用tr比简单替换要慢一些:

$ 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

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


4
由于OP有兴趣将修改后的值重新设置为OUTPUT,因此您必须考虑trsed解决方案有关的命令替换子Shell开销
iruvar 2014年

@ 1_CR是的,但是由于无论他使用哪种方法都会如此,我认为这是无关紧要的。
terdon

1
不完全,OUTPUT="${OUTPUT//[`\"\']/}" 不涉及命令替换
iruvar

@ 1_CR啊,我知道,是的,您说得对,这确实改变了结果。谢谢,答案已编辑。
terdon

2
涉及命令替换的方法的缺点是有些麻烦。(您可以避免使用它,但要以使命令复杂得多的代价为代价。)特别是,命令替换会删除尾随的换行符。
吉尔斯(Gilles)'所以

15

您可以使用变量替换

$ OUTPUT=a\'b\"c\`d
$ echo "$OUTPUT"
a'b"c`d

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

$ echo "${OUTPUT//\'/x}"
axb"c`d
$ echo "${OUTPUT//\"/x}"
a'bxc`d
$ echo "${OUTPUT//\`/x}"
a'b"cxd
$ echo "${OUTPUT//[\'\"\`]/x}"
axbxcxd

@ rubo77 echo ${OUTPUT//[`\"\']/x}axbxcxa
混乱

将扩展命名为“可变扩展”是不正确的。这称为“参数扩展”。
gena2x 2014年

@ gena2x-我不明白您的评论在这里意味着什么?
slm

12

在bash或zsh中,它是:

OUTPUT="${OUTPUT//[\`\"\']/}"

请注意,${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

1
其实tr更快。正则表达式和glob非常昂贵,尽管这里没有外部程序,但bash总是比慢tr
terdon

这在很大程度上取决于输入数据和regexp实现。在回答中,您选择了一些特定的大型数据集-但数据集可能很小。或不同。另外,您不测量正则表达式的时间,而是回声的时间,所以我不确定您的比较是否真的公平。
gena2x 2014年

好点。但是,未经测试就无法宣称速度。实际上,当分配一个变量时,这似乎更快,但是在屏幕上打印时tr胜出(请参阅我的答案)。我同意这将取决于许多因素,这就是为什么您不经过实际测试就无法分辨出哪一个获胜的原因。
terdon

6

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

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

该函数外壳会引用您递给它的任何arg数组,并为每个可迭代参数增加其输出。

这里有一些参数:

aq \
"here's an
ugly one" \
"this one is \$PATHpretty bad, too" \
'this one```****```; totally sucks'

输出值

1='here'"'"'s an
ugly one'
2='this one is $PATHpretty bad, too'
3='this one```****```; totally sucks'

该输出dash通常从那里安全地引用单引号输出,例如'"'"'bash会做'\''

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

set -f; IFS=\"\'\`; set -- $var; printf %s "$*"

输出值

"some ""crazy """"""""string ""here

在那里,我就printf可以看到它,但是如果我这样做了,当然可以:

var="$*"

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

当我set -f指示外壳进行球形转换时(如果字符串包含可被解释为球形转换样式的字符)。我这样做是因为shell解析器对变量执行字段拆分之后会扩展glob模式。可以像一样重新启用globing set +f。总的来说-在脚本中-我发现像这样设置爆炸效果很有用:

#!/usr/bin/sh -f

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

根据中的字符进行字段拆分$IFS

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

IFS=\ ; var='      '; printf '<%s>' $var
<>

但是,所有其他所有对象都指定为每次出现时对一个字段求​​值-它们不会被截断。

IFS=/; var='/////'; printf '<%s>' $var
<><><><><>

默认情况下,所有变量扩展都是$IFS定界的数据数组-它们根据拆分为单独的字段$IFS。当您用"引号引起来时,您将覆盖该数组属性并将其评估为单个字符串。

所以当我做...

IFS=\"\'\`; set -- $var

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

"$*"像其他双引号变量展开式一样,也覆盖的字段拆分性质$IFS。但是,除此之外,它替换的第一个字节$IFS 为每个分隔的领域"$@"。因此,因为所有后续定界符中"第一个$IFS 都变为"in "$*""不必是$IFS当你把它分解,无论是。你可以改变$IFS set -- $args到另一个价值完全和它的新的,然后第一个字节将出现在现场分隔符"$*"。此外,您可以完全删除它们的所有痕迹,例如:

set -- $var; IFS=; printf %s "$*"

输出值

some crazy string here

非常好,+ 1。我想知道它是否确实更快。您能否添加一些计时测试,将其与我的答案中的方法进行比较?我希望您的速度会更快,但希望看到。
terdon

@terdon-取决于外壳。这是几乎可以肯定的速度比tr任何壳,但不同的是在前途未卜bash${var//$c/$newc/}情况下。我希望即使在这种情况下,速度也会有所提高,但是我通常不必担心,因为我经常使用这种东西dash-通常在各个方面都快几个数量级。因此很难比较。
mikeserv

@terdon-我尝试过。但是-即使是在bash-做time (IFS=\"\'`; set -- $var; printf %s "$*")time (var=${var//\'`/\"/})两个结果0.0000s结果所有领域。我在做错什么吗,您认为呢?在反引号之前应该有一个反斜杠,但我不知道如何在注释代码字段中加上反引号。
mikeserv
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.