2

我有一个 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可能被合理地别名为(例如bashsh仿真模式下)。
  • 它必须尽可能便携(Windows 除外)。
  • 使用perl是不可接受的,但可以使用随处可见的其他 unix 工具。
4

2 回答 2

5

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
}
于 2012-11-13T01:35:33.650 回答
2

以下解决方案适用于Bourne shell 可以处理的所有输入字符串(包括换行符),在大多数系统上不使用外部命令,并且可移植到所有现代 Bourne-like shell:放入此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}替换(所有你需要关心的 shell,除非你的目标是 Solaris 10/bin/sh或类似的古代遗物)。
  2. 上面的代码不需要 fork/exec 任何新进程,除非printf它不是你的 shell 中的内置程序(printf仅作为外部命令可用的情况很少见,例如sed,它几乎总是一个外部命令,我已经看到更多精简的系统有 aprintf但没有 a sed,如果这很重要的话)。

unescaped注意:此处发布的版本将一个变量泄漏到全局命名空间local unescaped(花括号,虽然如果你走那条路,这在视觉上有点不明显,而且大多数 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'`"

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

于 2016-09-06T06:42:49.530 回答