从sh内部生成sh代码:转义 [英] Generating sh code from within sh: Escaping

查看:151
本文介绍了从sh内部生成sh代码:转义的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a shell variable (we shall call x) containing a string with shell meta characters. For example, I might have

abc  "def's"  ghi

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

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

I want to build a shell command from that string (to be stored in a file, not executed). What are my options?

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

输出如下(尽管等效输出也可以接受) ):

The output is as follows (although equivalent output is also acceptable):

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

问题是需要完成的地方数量正在增加。有没有人有更好的解决方案?

The problem is that the number of places this needs to be done is increasing. Does anyone have a better solution?

注意:


  • 必须为 sh (而不是 bash )和任何 sh 别名(例如,在 sh 仿真模式下为 bash )。

  • 它必须尽可能的可移植(不包括Windows)。

  • 使用 perl 是不可接受的,但是可以使用其他的unix工具无处不在

  • Must work for sh (as opposed to bash) and anything sh might reasonably be aliased to (e.g. bash in sh emulation mode).
  • It must be as portable as possible (Windows excluded).
  • Use of perl is not acceptable, but the use of other unix tools found everywhere might be.

推荐答案

以下解决方案适用于Bourne shell可以处理的 ALL 输入字符串(包括 newline 字符),在大多数系统上不使用外部命令,并且可移植到所有类似Bourne的现代shell中:每个参数放在此 esceval 函数中的字符将分别正确地转义/引用。 (如果您想知道,我将其命名为 esceval 以进行转义评估。)

The following solution will work on ALL input strings that the Bourne shell can handle (including newline characters), uses no external commands on most systems, and is portable to all modern Bourne-like shells: Each argument put into this esceval function is printed individually properly escaped/quoted. (I named it esceval for "escape evaluation", in case you were wondering.)

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

以上关于我的保证的详细研究:

More pedantic elaboration on my guarantees above:


  1. 以上代码可移植到所有具有功能的shell (今天所有实际使用的shell)以及 $ {foo#bar} $ {foo %% bar} 替换(除非您要针对Solaris 10的 / bin / sh 或类似的古代遗物,否则您需要关心的所有外壳)。

  2. 除非 printf 不是您的shell内置程序,否则上面的代码将不需要派生/执行任何新进程(对于 printf 仅可作为外部命令使用,例如 sed 几乎总是一个外部命令,而我看到了更多的精简系统 printf ,但没有 sed (如果有的话)。

  1. The above code is portable to all shells that have functions (all shells in practical use today) and the ${foo#bar} and ${foo%%bar} substitutions (all shells you need to care about unless you're targetting Solaris 10's /bin/sh or similar ancient relics).
  2. The above code will not need to fork/exec any new processes unless printf is not a builtin in your shell (fairly uncommon for printf to be only available as an external command, vs. for instance sed which is almost always an external command, and I've seen more stripped-down systems which have a printf but don't have a sed, if that matters).

注意:此处发布的版本会将一个变量泄漏到全局变量n中amespace(未转义的),但是如果您的外壳程序支持本地未转义的,则可以轻松解决此问题,或者将函数的主体包装在一个子shell中(括号-它们甚至可以替换花括号,尽管如果按照这种方法,这在视觉上并不明显,并且大多数shell都会为该子shell分叉一个额外的过程)。

Note: The version posted here leaks one variable into the global namespace (unescaped), but you can easily fix this by either declaring local unescaped if your shell supports that, or by wrapping the body of the function in a subshell (parentheses - they can even replace the curly braces, though that's a bit visually non-obvious if you go that route, and most shells do fork an additional process for a subshell).

另一方面,如果有可能您要做需要支持没有这些可变子字符串替换的系统,则可以使用 sed ,但您需要小心避免字符串中的诸如换行符之类的棘手内容:

On the other hand, if by some chance you do need to support systems which don't have those variable substring substitutions, you can use sed, but you need to be careful to properly escape tricky things like newlines in your strings:

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去除一条)。换句话说,请对此感到疲倦:

Final note on trailing newlines: you should note that Bourne shells strip trailing newlines from command substitutions (most strip all trailing newlines, a few shells strip just one). In other words, be weary of this:

# 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在看到它们时不被推翻): https://github.com/mentalisttraceur/esceval/blob/master/sh/esceval.sh

P.S. There's also this monstrosity I made, which is a polyfill which will select between the previous two versions depending on if your shell seems to be capable of supporting the necessary variable substitution syntax (it looks awful though, because the shell-only version has to be inside an eval-ed string to keep the incompatible shells from barfing when they see it): https://github.com/mentalisttraceur/esceval/blob/master/sh/esceval.sh

这篇关于从sh内部生成sh代码:转义的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆