POSIX SH等效猛砸的printf%Q(POSIX sh equivalent for Bash

2019-06-27 12:13发布

假设我有一个#!/bin/sh脚本,可以采取各种位置参数,其中一些可能会包含空格,要么/这两种报价,等我想迭代"$@" ,并为每一个参数任一过程立即不知何故,或保存以后。 当时我想,启动脚本的末尾(也许exec )其他进程,传递某些参数与完整的所有特殊字符。

如果我做的参数没有处理, othercmd "$@"会工作得很好,但我需要拉出一些参数,并进行处理了一下。

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

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

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

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

工作在很多情况下:

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

而不是在'使用:

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

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

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

Answer 1:

这是绝对可行的。

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

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

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

或者,而不是逃逸,使它们成为嵌入式单引号:

'"'"'

..instead你可以把它们分为:

'\''

..stylistic差异我想(我想象的性能差异可以忽略不计无论哪种方式,虽然我从来没有测试过)。 得到的SED字符串如下所示:

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

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

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

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

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

如果你不好吗与sed的依赖,但你罚款假设你的外壳实际上是符合POSIX标准(仍有一些在那里,特别是在Solaris 10和下面的/ bin / sh的,不会能够做到这下变型 - 但是你需要关心会这么做就好了),几乎所有的炮弹:

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

您可能注意到看似多余这里引用:

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

..this可以替换为:

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

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

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

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

“别急”,你说:“我想在一个命令这样做对多个参数什么,我所要的输出仍然看起来有点漂亮,清晰的,我作为一个用户,如果我从不管出于什么原因在命令行中运行呢? “。

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

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

..或者同样的事情,但唯一的外壳版本:

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

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

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



Answer 2:

我认为这是POSIX。 它的工作原理通过清除$@扩大它在for循环之后,但只有一次,让我们可以使用迭代建立它的备份(反向) set

flag=0
for i in "$@"; do
    [ "$flag" -eq 0 ] && shift $#
    set -- "$i" "$@"
    flag=1
done

echo "$@"   # To see that "$@" has indeed been reversed
ls "$@"

我实现逆转的论据只是一个例子,但是你可以用的这一招set -- "$arg" "$@"set -- "$@" "$arg"中的其他情形。

是的,我意识到我可能只是重新实现(很差)ormaaj的推送。



Answer 3:

推 。 示例请参见自述。



Answer 4:

下面似乎一切我已经在它为止抛出,包括空格,这两种报价和其他各种元字符和嵌入式换行符的工作:

#!/bin/sh
quote() {
    echo "$1" | sed "s/'/'\"'\"'/g"
}
args=
for arg in "$@"
do
    argq="'"`quote "$arg"`"'"
    args="$argq $args"
done
eval "ls $args"


文章来源: POSIX sh equivalent for Bash’s printf %q