【问题标题】:Generating sh code from within sh: Escaping从 sh 中生成 sh 代码:转义
【发布时间】:2012-11-13 01:11:40
【问题描述】:

我有一个 shell 变量(我们称之为x),其中包含一个带有 shell 元字符的字符串。例如,我可能有

abc  "def's"  ghi

按照设置

x='abc  "def'\''s"  ghi'

我想从该字符串构建一个 shell 命令(存储在文件中,不执行)。我有哪些选择?

echo "prog $x"     >>file     # Doesn't work
echo "prog '$x'"   >>file     # Doesn't work
echo "prog \"$x\"" >>file     # Doesn't work

目前的解决方案使用sed

y=`echo "$x" | sed 's/\([^a-zA-Z0-9._\-\/]\)/\\\\\1/g'`
echo "prog $y" >>file

输出如下(虽然等效输出也可以):

prog abc\ \ \"def\'s\"\ \ ghi

问题是需要做的地方越来越多。谁有更好的解决方案?

注意事项:

  • 必须为 sh(而不是 bash)工作,并且任何 sh 都可以合理地别名为(例如,sh 仿真模式下的 bash)。
  • 它必须尽可能便携(Windows 除外)。
  • 使用perl 是不可接受的,但随处可见的其他 unix 工具的使用可能是。

【问题讨论】:

  • 或许这里的功能会起作用? stackoverflow.com/a/12162717/1013153
  • @Kiyura,谢谢,从中得出了一个解决方案(见下文)。使用单引号看起来更干净,但我想尽可能少地摇晃船,因为这是perl 的安装程序。大多数时候,根本不需要转义。

标签: escaping sh


【解决方案1】:

sh 有函数。

# to_shell_lit() - Creates a shell literal
# Usage:  printf '%s\n' "...$( quote "..." )..."
to_shell_lit() {
    printf \'
    printf %s "$1" | sed "s/'/'\\\\''/g"
    printf \'
}

测试:

$ x='abc  "def'\''s"  ghi'

$ printf '%s\n' "$x"
abc  "def's"  ghi

$ printf '%s\n' "prog `to_shell_lit "$x"`"
prog 'abc  "def'\''s"  ghi'

$ printf '%s\n' "prog $( to_shell_lit "`pwd`" )"
prog '/home/ikegami/foo bar'
$ printf '%s\n' "$( to_shell_lit "a'b" )"
'a'\''b'

$ printf '%s\n' "$( to_shell_lit '-n' )"
'-n'

$ printf '%s\n' "$( to_shell_lit '\\' )"
'\\'

$ printf '%s\n' "$( to_shell_lit 'foo
bar' )"
'foo
bar'

采用多个参数的版本:

# to_shell_lit() - Creates a shell literal
# Usage:  printf '%s\n' "$( to_shell_lit "..." "..." "..." )"
to_shell_lit() {
    local prefix=''
    local p
    for p in "$@" ; do
        printf "$prefix"\'
        printf %s "$p" | sed "s/'/'\\\\''/g"
        printf \'
        prefix=' '
    done
}

【讨论】:

  • 很好的解决方案,可用于多种用途。但是当输入字符串嵌入换行符时,和/或当输入匹配echo 选项或转义序列时,它会中断,例如:quote "-n"(不打印)或quote '\\'(应该打印\\\`), or on systems where echo does backslash escapes by default, quote '\ 0101'`(打印A)。
  • @mtraceur,已修复。通过将echo 替换为printf,解决了其中两个问题。另一个是由于正则表达式中的错误字符类。最后,换行符通过从反斜杠切换到单引号字符串来解决。
【解决方案2】:

以下解决方案适用于 Bourne shell 可以处理的 ALL 输入字符串(包括 换行 字符),在大多数系统上不使用外部命令,并且可移植到所有现代 Bourne 式外壳:放入此 esceval 函数的每个参数都单独打印,并正确转义/引用。 (我将它命名为 esceval 用于“逃避评估”,以防您想知道。)

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"
}

对我上面的保证进行更迂腐的阐述:

  1. 上面的代码可移植到所有具有功能的 shell(今天实际使用的所有 shell)和 ${foo#bar}${foo%%bar} 替换(除非你的目标是 Solaris 10 的 /bin/sh,否则你需要关心的所有 shell或类似的古代遗物)。
  2. 上述代码不需要 fork/exec 任何新进程,除非 printf 不是您的 shell 中的内置函数(printf 仅作为外部命令可用,而 sed这几乎总是一个外部命令,我见过更多精简的系统,它们有 printf 但没有 sed,如果这很重要的话)。

注意:此处发布的版本将一个变量泄漏到全局命名空间 (unescaped),但您可以通过声明 local unescaped(如果您的 shell 支持)或将函数的主体包装在subshel​​l(括号 - 它们甚至可以替换花括号,但如果你走这条路线,这在视觉上有点不明显,而且大多数 shell 确实为子 shell 分叉了一个额外的过程)。

另一方面,如果您确实需要支持没有这些变量子字符串替换的系统,您可以使用sed,但您需要小心正确避免字符串中的换行等棘手的事情:

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"
}

关于尾随换行的最后说明:您应该注意,Bourne shell 从命令替换中删除尾随换行(大多数会删除所有尾随换行,少数 shell 只删除一个)。换句话说,请对此感到厌倦:

# Literal strings work fine:
esceval 'foo



'

# Quoted variable substitution also works fine:
ln='
'
esceval "foo$ln$ln$ln$ln"

# Breaks - newlines never make it into `esceval`:
esceval "`printf 'foo\n\n\n\n'`"

附:我还做了这个怪物,它是一个 polyfill,它将在前两个版本之间进行选择,具体取决于你的 shell 是否能够支持必要的变量替换语法(虽然看起来很糟糕,因为只有 shell 的版本必须是在一个 eval-ed 字符串中,以防止不兼容的 shell 在看到它时吐出):https://github.com/mentalisttraceur/esceval/blob/master/sh/esceval.sh

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-26
    • 2011-09-21
    • 1970-01-01
    • 1970-01-01
    • 2015-07-29
    • 2020-06-03
    相关资源
    最近更新 更多