从sh内生成sh代码:转义

时间:2012-11-13 01:11:41

标签: escaping sh

我有一个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可能会合理地混淆(例如bash仿真模式中的sh
  • 必须尽可能便携(不包括Windows)。
  • 使用perl是不可接受的,但使用无处不在的其他unix工具

2 个答案:

答案 0 :(得分:3)

sh有功能。

# quote() - Creates a shell literal
# Usage:  printf '%s\n' "...$( quote "..." )..."
quote() {
    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 `quote "$x"`"
prog 'abc  "def'\''s"  ghi'

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

$ printf '%s\n' "$( quote "a'b" )"
'a'\''b'

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

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

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

带有多个参数的版本:

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

答案 1 :(得分:1)

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

    注意:此处发布的版本会将一个变量泄漏到全局命名空间(unescaped),但您可以通过声明local unescaped(如果您的shell支持)或通过包装正文来轻松解决此问题。子shell中的函数(括号 - 它们甚至可以替换花括号,但如果你走这条路线,这在视觉上是不明显的,并且大多数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'`"
    

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