bash - 快速转义任何字符的字符串

3
我正在寻找一个能够快速转义字符串的工具。这个任务非常有用,但我找不到它。
例如:
hisrmline 'h | g -E "^ [0-9]*  exit$"'

如果我想要手动转义,可以这样做:
'hisrmline '\''h | g -E "^ [0-9]*  exit$"'\'''

但这很费时间,效率也不高。所以我找到了printf %q

[xiaobai@xiaobai note]$ printf "%q" hisrmline 'h | g -E "^ [0-9]*  exit$"'
hisrmlineh\ \|\ g\ -E\ \"\^\ \[0-9\]\*\ \ exit\$\"[xiaobai@xiaobai note]$ 
[xiaobai@xiaobai note]$ 

输出结果错误是因为hisrmlineh被拼接在一起了,所以我缩小了字符串范围:

[xiaobai@xiaobai note]$ printf "%q" hisrmline 'h'
hisrmlineh[xiaobai@xiaobai note]$ 
[xiaobai@xiaobai note]$ 

我需要的是 hisrmline\ \'h\'

在使用 grep 命令时,这个功能特别有用:

[xiaobai@xiaobai note]$ HISTTIMEFORMAT=""; history|grep -a --color=auto hisrmline\ \'h
 7856  hisrmline 'hisrmline'
 7857  hisrmline 'hisrmline'
 7882  hisrmline 'h | g -E "^ [0-9]*  exit[ ]*$"'
 7883  hisrmline 'h | g -E "^ [0-9]*  exit[ ]*$"'
 7884  hisrmline 'h | g -E "'
 7885  hisrmline 'h | g '
 7886  hisrmline 'h | g'
 7887  hisrmline 'h |'
 7890  hisrmline 'h | g -E "^ [0-9]*  exit$"'
 7891  hisrmline 'h | g -E "^ [0-9]*  exit$"'
 7905  h|g 'hisrmline 'h | g -E "^ [0-9]*  exit$"''

使用 grep -F 在处理嵌套的单引号时并不能让我的生活更轻松,我仍然需要手动转义单引号 '\'':

[xiaobai@xiaobai note]$ HISTTIMEFORMAT=""; history|grep -a --color=auto -F  '[0-9]*  exit$"'\'''
 7889  h|g -aF 'h | g -E "^ [0-9]*  exit$"'
 7890  hisrmline 'h | g -E "^ [0-9]*  exit$"'
 7891  hisrmline 'h | g -E "^ [0-9]*  exit$"'
 7905  h|g 'hisrmline 'h | g -E "^ [0-9]*  exit$"''
 7911  h|g 'hisrmline 'h | g -E "^ [0-9]*  exit$"''
 7912  h|g 'hisrmline '"'"'h | g -E "^ [0-9]*  exit$"'"'"'

有没有更简单的方法或者现有的工具可以转义包含任意字符的字符串列表?

3个回答

1
如果您正确引用命令行,则printf应该能够正常工作,例如:
printf "%q\n" "hisrmline 'h'"
hisrmline\ \'h\'

或者:

printf "%q\n" "hisrmline 'h | g -E \"^ [0-9]*  exit$\"'"
hisrmline\ \'h\ \|\ g\ -E\ \"\^\ \[0-9\]\*\ \ exit\$\"\'

编辑:您可能正在寻找这种转义方式:

IFS= read -r str<<"EOF"
hisrmline 'h | g -E "^ [0-9]*  exit$"'
EOF

printf "%q\n" "$str"
hisrmline\ \'h\ \|\ g\ -E\ \"\^\ \[0-9\]\*\ \ exit\$\"\'

[由@林果皞更新]

对于可能感兴趣的人,必须将EOF引用以防止扩展,正如@bize所指出的那样:

未引用的EOF:

[xiaobai@xiaobai Downloads]$ IFS= read -r str<<EOF
> target='h | g -E -i -e "^[ ]+[0-9]+  .*[|&; ]+g[ ]" -e "^[ ]+[0-9]+  .*[|&; ]+g$"'; history|grep -aF "$target"; echo ${#target}
> EOF
[xiaobai@xiaobai Downloads]$ printf "%q\n" "$str"
target=\'h\ \|\ g\ -E\ -i\ -e\ \"\^\[\ \]+\[0-9\]+\ \ .\*\[\|\&\;\ \]+g\[\ \]\"\ -e\ \"\^\[\ \]+\[0-9\]+\ \ .\*\[\|\&\;\ \]+g\$\"\'\;\ history\|grep\ -aF\ \"h\ \|\ g\ -E\ -i\ -e\ \"\^\[\ \]+\[0-9\]+\ \ .\*\[\|\&\;\ \]+g\[\ \]\"\ -e\ \"\^\[\ \]+\[0-9\]+\ \ .\*\[\|\&\;\ \]+g\$\"\"\;\ echo\ 73
[xiaobai@xiaobai Downloads]$ 

"EOF" quoted:
[xiaobai@xiaobai Downloads]$ IFS= read -r str<<"EOF"
> target='h | g -E -i -e "^[ ]+[0-9]+  .*[|&; ]+g[ ]" -e "^[ ]+[0-9]+  .*[|&; ]+g$"'; history|grep -aF "$target"; echo ${#target}
> EOF
[xiaobai@xiaobai Downloads]$ printf "%q\n" "$str"
target=\'h\ \|\ g\ -E\ -i\ -e\ \"\^\[\ \]+\[0-9\]+\ \ .\*\[\|\&\;\ \]+g\[\ \]\"\ -e\ \"\^\[\ \]+\[0-9\]+\ \ .\*\[\|\&\;\ \]+g\$\"\'\;\ history\|grep\ -aF\ \"\$target\"\;\ echo\ \$\{#target\}
[xiaobai@xiaobai Downloads]$ 

只有在从引用的“EOF”输出提供时才会出现正确的行为。
[xiaobai@xiaobai Downloads]$ h|g -F target=\'h\ \|\ g\ -E\ -i\ -e\ \"\^\[\ \]+\[0-9\]+\ \ .\*\[\|\&\;\ \]+g\[\ \]\"\ -e\ \"\^\[\ \]+\[0-9\]+\ \ .\*\[\|\&\;\ \]+g\$\"\'\;\ history\|grep\ -aF\ \"\$target\"\;\ echo\ \$\{#target\}
 7721  target='h | g -E -i -e "^[ ]+[0-9]+  .*[|&; ]+g[ ]" -e "^[ ]+[0-9]+  .*[|&; ]+g$"'; history|grep -aF "$target"; echo ${#target}
 7725  target='h | g -E -i -e "^[ ]+[0-9]+  .*[|&; ]+g[ ]" -e "^[ ]+[0-9]+  .*[|&; ]+g$"'; history|grep -aF "$target"; echo ${#target}
 7726  atarget='h | g -E -i -e "^[ ]+[0-9]+  .*[|&; ]+g[ ]" -e "^[ ]+[0-9]+  .*[|&; ]+g$"'; history|grep -aF "$target"; echo ${#target}
 8297  target='h | g -E -i -e "^[ ]+[0-9]+  .*[|&; ]+g[ ]" -e "^[ ]+[0-9]+  .*[|&; ]+g$"'; history|grep -aF "$target"; echo ${#target}
 8320  target='h | g -E -i -e "^[ ]+[0-9]+  .*[|&; ]+g[ ]" -e "^[ ]+[0-9]+  .*[|&; ]+g$"'; history|grep -aF "$target"; echo ${#target}

*h被重命名为export HISTTIMEFORMAT=""; history

*g被重命名为grep -a --color=auto

直接使用$ h | g -F "$str"也可以工作。

当处理Unicode时,我必须将LC_ALL赋值为空(即LC_ALL="en_US.utf8"),然后查询(history,ls等)源字符串。 然后,我必须将其切换到LC_ALL = C以使printf %q正常工作。


谢谢您的回答,但我的问题是寻找一种更简单/快速/快捷的方法,而不必手动导航字符串并逐个添加"。我很惊讶,因为我甚至在Google上找不到这种实用工具。只需复制字符串并粘贴以进行转换,而无需为每个双引号"的出现手动添加反斜杠\。 - 林果皞
不,你不必手动逐个转义每个 ",请看我的更新答案。你可以将整个字符串读入变量 str 中,然后简单地使用:printf "%q\n" "$str" - anubhava

1

更新:

在评论中,您说您从历史记录中复制了几行并希望将它们重新插入到一个shell命令中。在bash中,有历史扩展来访问历史记录的部分或修改它。可能这已经是您想要的了。

否则,您可以创建一个小命令来显示已转义的历史记录:

IFS=$'\n' history | while read line ; do printf "%q\n" "$line"; done

你可以从输出中复制行,并将它们插入到shell字符串中。如果 $HISTSIZE 很大,你还可以将其通过管道传输到 less 中。
如果你经常需要使用此命令,则可以将其创建为脚本文件或在 .bashrc 中创建函数。

原始回答

假设您想使用'作为字符串分隔符,可以使用以下bash表达式:

string="hisrmline 'h | g -E \"^ [0-9]*  exit$\"'"
echo "${string//\'/\\\'}"

现在你可以在bash中使用该字符串。如果您想在grep或其他使用正则表达式的程序中使用它,您需要进一步转义字符。不过,grep支持选项-F。如果您传递它,模式将被视为固定字符串,而不是正则表达式。

我知道grep -F,但我的问题是寻找一种更简单/快速/更快的方法,而不必手动导航字符串并逐个添加' " ' " ' OR ' ' '。我正在寻找printf %q list_of_strings,但正如您在我的问题中所看到的那样,当处理单引号时它无法正常工作。 - 林果皞
字符串来自哪里? - hek2mgl
对于我的情况来说,这是来自我的历史的。 - 林果皞
是的,我已经编写了一个函数放在~/.bashrc中,用于通过精确匹配删除历史记录。我只需要输入$hisrmline并粘贴参数hisrmline 'h | g -E "^ [0-9]* exit[ ]*$"',它就会正确地删除这行。但是我必须手动编辑参数以进行转义。 - 林果皞
1
你尝试过使用历史交互命令吗?这可能是你正在寻找的。 - hek2mgl
嗨,我尝试了你的IFS=$'\n' history...方法,但是当处理包含反斜杠(应该使其变成\\)的历史记录时失败了,例如:h| g -F target=\'h\ \|\ g\ -E\ -i\ -e\ \"\^\[\ \]+\[0-9\]+\ \ .\*\[\|\&\;\ \]+g\[\ \]\"\ -e\ \"\^\[\ \]+\[0-9\]+\ \ .\*\[\|\&\;\ \]+g\$\"\'\;\ history\|grep\ -aF\ \"h\ \|\ g\ -E\ -i\ -e\ \"\^\[\ \]+\[0-9\]+\ \ .\*\[\|\&\;\ \]+g\[\ \]\"\ -e\ \"\^\[\ \]+\[0-9\]+\ \ .\*\[\|\&\;\ \]+g\$\"\"\;\ echo\ 73 - 林果皞

1
一个简单方法:
printf "%q" "$(cat <<"_up_to_here_"
hisrmline 'h | g -E "^ [0-9]*  exit$"'
_up_to_here_
)"

"_up_to_here_"_up_to_here_之间的任何内容都将被正确引用。

请注意: 第一个_up_to_here_被引用,以防止在下一行或多行中展开任何$变量。

注释:使用cat旨在使命令保持简单,任何尝试正确转换为read都需要广泛的知识:这不是KISS方法。


非常感谢您,"引用以防扩展"作为@anubhava答案的伴侣非常重要,EOF应该被引用。但我只能接受一个答案 :) 对于那些在处理Unicode时可能感兴趣的人,我必须将LC_ALL=分配为空,然后查询(历史记录,ls..etc)源字符串。然后我必须将其切换到LC_ALL=C以使printf %q正常工作。 - 林果皞

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接