POSIX SH相当于猛砸的printf%Q [英] POSIX sh equivalent for Bash’s printf %q

查看:146
本文介绍了POSIX SH相当于猛砸的printf%Q的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有一个#!/ bin / sh的脚本可以采取各种位置参数,其中一些可能包括空格,要么/两种报价,等我想遍历$ @并为每个论述,无论是过程立即以某种方式,或保存供以后使用。在脚本的最后,我要启动(也许 EXEC )的另一个进程,传递一些参数与所有特殊字符不变。

如果我是对的参数做任何处理, othercmd$ @将工作很好,但我需要拉一些参数和处理他们一点。

如果我能承担猛砸,然后我可以使用的printf%Q 来计算args来引用版本,我可以评估以后,但这不会对例如工作Ubuntu的短跑( / bin / sh的)。

是否有任何相当于的printf%Q 可以在一个普通的Bourne shell脚本编写,只使用内置插件和POSIX定义的事业,也就是说,作为一个功能我可以复制到一个脚本?

例如,一个脚本试图 LS 它以相反的顺序参数:

 #!/ bin / sh的
ARGS =
在ARG$ @

    ARGS ='$ ARG'的$ args
DONE
EVALLS的$ args

适用于许多情况:

  $ ./handle再见残酷的世界
LS:不能访问残酷的世界:没有这样的文件或目录
LS:不能访问再见:没有这样的文件或目录

而不是在 时:

  $ ./handle再见cruel'st世界
./handle:1:EVAL:语法错误:未终止的​​引号的字符串

和下面的工作正常,但依靠击:

 #!/斌/庆典
ARGS =
在ARG$ @

    printf的-v argq'%Q'$ ARG
    ARGS =$ argq的$ args
DONE
EVALLS的$ args


解决方案

这是绝对可行的。

您杰西·格里克看到的答案是大约存在,但它有几个错误的,我有你的考虑一些更多的选择,因为这是我遇到不止一次。

更大的问题

首先,你可能已经知道这一点,回声是一个坏主意,应该用printf相反,如果目标是可移植性:回声是未定义行为POSIX,如果它接收到的说法是-n,而在实践中的回声一些实现把-n作为一个特殊的选项,而其他人只是把它当作一个正常的参数进行打印。这样就显得这样的:

  esceval()
{
    的printf%的$ 1| SEDS /'/'\\'\\'/ G
}

另外,除了通过使它们变成逃逸嵌入式单引号:

 

..而不是你可以把它们变成:

 '\\''

..风格的差异我想(我想象的性能差异可以忽略不计无论哪种方式,虽然我从来没有测试过)。由此产生的SED字符串看起来是这样的:

  esceval()
{
    的printf%的$ 1| SEDS /'/'\\\\\\\\''/ G
}

(这四个反斜杠,因为双引号吞下两个人,而留下两个,然后SED燕子之一,只留下一个。就个人而言,我觉得这种方式更具有可读性所以这就是我会的其余部分使用例子涉及它,但两者应该是等价的。)

,但我们仍然有一个错误:命令替换将删除命令输出其后的换行符的至少一个(但在许多炮弹ALL)(不是所有的空白,特别是刚换行)。因此,上述解决方案的工作,除非你有一个说法的最后换行符(S)。然后你会失去/换行符那些(S)。解决方法是明显简单:从你的报价/ esceval功能输出前的实际控制值后添加另一个角色。顺便说一句,我们已经需要做的是,无论如何,因为我们需要启动和停止单引号转义的说法。老实说,我不明白为什么没有这样做开始。你有两个选择:

  esceval()
{
    printf的'%s的\\ n'$ 1| SEDS /'/'\\\\\\\\''/克; 1 S / ^ /'/; $ S / $ //
}

这将确保参数弄出来已经完全逃脱了,没有必要建设的最终字符串时增加更多的单引号。这可能是你会得到一个单一的,内联能够版本最接近。如果你没事用具有SED的依赖,你可以停在这里。

如果你不行的sed的依赖,但你罚款假设你的shell实际上是POSIX兼容的,(还是有一些在那里,特别是在Solaris 10及以下,/ bin / sh的这将不能够做到这下变型 - 但几乎所有的炮弹,你需要关心会这么做就好了):

  esceval()
{
    printf的\\'
    转义= $ 1
    同时:
    做
        案例$在转义
        * \\'*)
            的printf%的$ {转义%% \\'*}'\\''
            转义= $ {#转义* \\'}
            ;;
        *)
            的printf%的$转义
            打破
        ESAC
    DONE
    printf的\\'
}

您可能注意到看似多余的报价在这里:

 的printf%的$ {转义%% \\'*}'\\''

..这可以被替换为:

 的printf%的$ {转义%% \\'*}'\\''

我做了前者,唯一的原因是因为一个仙界有变量替换成带引号的字符串,其中围绕该变量的报价并没有完全开始和结束的变量替换在哪里时具有错误的Bourne外壳。因此,它是我的偏执便携习惯。在实践中,你可以做后者,它不会是一个问题。

如果您不想揍在你的shell环境的其余部分变量转义,那么你可以用该功能的全部内容在子shell,像这样:

  esceval()
{
  (
    printf的\\'
    转义= $ 1
    同时:
    做
        案例$在转义
        * \\'*)
            的printf%的$ {转义%% \\'*}'\\''
            转义= $ {#转义* \\'}
            ;;
        *)
            的printf%的$转义
            打破
        ESAC
    DONE
    printf的\\'
  )
}

别急,你说:我想在一个命令这样做对多个参数什么,我所要的输出还是看还挺不错,清晰的,我作为一个用户,如果我在命令行中运行呢?不管是什么原因。

不要害怕,我中有你覆盖:

  esceval()
{
    在0的情况下$#)返回0; ESAC
    同时:
    做
        printf的'
        的printf%的$ 1| SEDS /'/'\\\\\\\\''/ G
        转移
        在0的情况下$#)打破; ESAC
        printf的'
    DONE
    printf上的'\\ n
}

..或同样的事情,但与只壳版本

  esceval()
{
  在0的情况下$#)返回0; ESAC
  (
    同时:
    做
        printf的'
        转义= $ 1
        同时:
        做
            案例$在转义
            * \\'*)
                的printf%的$ {转义%% \\'*}'\\''
                转义= $ {#转义* \\'}
                ;;
            *)
                的printf%的$转义
                打破
            ESAC
        DONE
        在0的情况下$#)打破; ESAC
        printf的'
    DONE
    printf上的'\\ n
  )
}

在这过去的四年,你可能崩溃一些外部的printf语句,并推出了自己的单引号到另一个printf的 - 我让他们分开,因为我觉得它使逻辑更加清晰的时候可以看到起点和终点的单报价在单独的报表打印。

P.S。还有这个畸形我做了,这是一种填充工具,这将在previous之间进行选择取决于如果你的shell似乎是能够支持必要的变量替换语法(在两个版本中,它看起来可怕,虽然,因为只有外壳版本必须是一个EVAL-ED字符串内保持不兼容的炮弹barfing当他们看到它)的 https://github.com/mentalisttraceur/esceval/blob/master/sh/esceval.sh

Suppose I have a #!/bin/sh script which can take a variety of positional parameters, some of which may include spaces, either/both kinds of quotes, etc. I want to iterate "$@" and for each argument either process it immediately somehow, or save it for later. At the end of the script I want to launch (perhaps exec) another process, passing in some of these parameters with all special characters intact.

If I were doing no processing on the parameters, othercmd "$@" would work fine, but I need to pull out some parameters and process them a bit.

If I could assume Bash, then I could use printf %q to compute quoted versions of args that I could eval later, but this would not work on e.g. Ubuntu's Dash (/bin/sh).

Is there any equivalent to printf %q that can be written in a plain Bourne shell script, using only built-ins and POSIX-defined utilities, say as a function I could copy into a script?

For example, a script trying to ls its arguments in reverse order:

#!/bin/sh
args=
for arg in "$@"
do
    args="'$arg' $args"
done
eval "ls $args"

works for many cases:

$ ./handle goodbye "cruel world"
ls: cannot access cruel world: No such file or directory
ls: cannot access goodbye: No such file or directory

but not when ' is used:

$ ./handle goodbye "cruel'st world"
./handle: 1: eval: Syntax error: Unterminated quoted string

and the following works fine but relies on Bash:

#!/bin/bash
args=
for arg in "$@"
do
    printf -v argq '%q' "$arg"
    args="$argq $args"
done
eval "ls $args"

解决方案

This is absolutely doable.

The answer you see by Jesse Glick is approximately there, but it has a couple of bugs, and I have a few more alternatives for your consideration, since this is a problem I ran into more than once.

First, and you might already know this, echo is a bad idea, one should use printf instead, if the goal is portability: "echo" has undefined behavior in POSIX if the argument it receives is "-n", and in practice some implementations of echo treat -n as a special option, while others just treat it as a normal argument to print. So that becomes this:

esceval()
{
    printf %s "$1" | sed "s/'/'\"'\"'/g"
}

Alternatively, instead of escaping embedded single quotes by making them into:

'"'"'

..instead you could turn them into:

'\''

..stylistic differences I guess (I imagine performance difference is negligible either way, though I've never tested). The resulting sed string looks like this:

esceval()
{
    printf %s "$1" | sed "s/'/'\\\\''/g"
}

(It's four backslashes because double quotes swallow two of them, and leaving two, and then sed swallows one, leaving just the one. Personally, I find this way more readable so that's what I'll use in the rest of the examples that involve it, but both should be equivalent.)

BUT, we still have a bug: command substitution will delete at least one (but in many shells ALL) of the trailing newlines from the command output (not all whitespace, just newlines specifically). So the above solution works unless you have newline(s) at the very end of an argument. Then you'll lose that/those newline(s). The fix is obviously simple: Add another character after the actual command value before outputting from your quote/esceval function. Incidentally, we already needed to do that anyway, because we needed to start and stop the escaped argument with single quotes. Honestly, I don't understand why that wasn't done to begin with. You have two alternatives:

esceval()
{
    printf '%s\n' "$1" | sed "s/'/'\\\\''/g; 1 s/^/'/; $ s/$/'/"
}

This will ensure the argument comes out already fully escaped, no need for adding more single quotes when building the final string. This is probably the closest thing you will get to a single, inline-able version. If you're okay with having a sed dependency, you can stop here.

If you're not okay with the sed dependency, but you're fine with assuming that your shell is actually POSIX-compliant (there are still some out there, notably the /bin/sh on Solaris 10 and below, which won't be able to do this next variant - but almost all shells you need to care about will do this just fine):

esceval()
{
    printf \'
    UNESCAPED=$1
    while :
    do
        case $UNESCAPED in
        *\'*)
            printf %s "${UNESCAPED%%\'*}""'\''"
            UNESCAPED=${UNESCAPED#*\'}
            ;;
        *)
            printf %s "$UNESCAPED"
            break
        esac
    done
    printf \'
}

You might notice seemingly redundant quoting here:

printf %s "${UNESCAPED%%\'*}""'\''"

..this could be replaced with:

printf %s "${UNESCAPED%%\'*}'\''"

The only reason I do the former, is because one upon a time there were Bourne shells which had bugs when substituting variables into quoted strings where the quote around the variable didn't exactly start and end where the variable substitution did. Hence it's a paranoid portability habit of mine. In practice, you can do the latter, and it won't be a problem.

If you don't want to clobber the variable UNESCAPED in the rest of your shell environment, then you can wrap the entire contents of that function in a subshell, like so:

esceval()
{
  (
    printf \'
    UNESCAPED=$1
    while :
    do
        case $UNESCAPED in
        *\'*)
            printf %s "${UNESCAPED%%\'*}""'\''"
            UNESCAPED=${UNESCAPED#*\'}
            ;;
        *)
            printf %s "$UNESCAPED"
            break
        esac
    done
    printf \'
  )
}

"But wait", you say: "What I want to do this on MULTIPLE arguments in one command? And I want the output to still look kinda nice and legible for me as a user if I run it from the command line for whatever reason."

Never fear, I have you covered:

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

..or the same thing, but with the shell-only version:

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
        case $# in 0) break; esac
        printf "' "
    done
    printf "'\n"
  )
}

In those last four, you could collapse some of the outer printf statements and roll their single quotes up into another printf - I kept them separate because I feel it makes the logic more clear when you can see the starting and ending single-quotes on separate print statements.

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

这篇关于POSIX SH相当于猛砸的printf%Q的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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