微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

POSIX sh等于Bash的printf%q

假设我有一个#!/ bin / sh脚本,它可以使用各种位置参数,其中一些可能包含空格,两种/两种引号等.我想迭代“$@”,每个参数都要进行处理它立即以某种方式,或保存为以后.在脚本结束时,我想启动(或许执行)另一个进程,传递一些这些参数,所有的特殊字符都是完整的.

如果我没有对参数进行任何处理,othercmd“$@”可以正常工作,但是我需要提取一些参数并进行处理.

如果我可以假设Bash,那么我可以使用printf%q来计算引用的args版本,我可以稍后评估,但是这不会在例如Ubuntu的破折号(/ bin / sh).

有没有相当于printf%q可以用普通的Bourne shell脚本编写,只使用内置函数和POSIX定义的实用程序,作为我可以复制到脚本中的函数

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

#!/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

并且以下工作正常但依赖于Bash:

#!/bin/bash
args=
for arg in "$@"
do
    printf -v argq '%q' "$arg"
    args="$argq $args"
done
eval "ls $args"
这是绝对可行的.

Jesse Glick看到的答案大概在那里,但是它有几个错误,而且我还有几个替代方案供您考虑,因为这是一个不止一次遇到的问题.

首先,您可能已经知道这一点,回声是一个坏主意,应该使用printf,如果目标是可移植性:“echo”在POSIX中有未定义的行为,如果接收到的参数是“-n”,而在实践中echo treat -n的实现作为一个特殊选项,而其他只将其视为打印的正常参数.所以成为这样

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

或者,而不是通过将嵌入式单引号转换为:

'"'"'

..你可以把它们变成:

'\''

我猜想(我想象,性能差异可以忽略不计,尽管我从来没有测试过).所得到的sed字符串如下所示:

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

(它是四个反斜杠,因为双引号吞下了两个,并留下两个,然后sed吞下一个,只留下一个.就个人而言,我觉得这样更可读,所以我将在其余的例子中使用它,但两者应该是等效的.)

但是,我们仍然有一个错误:命令替换将从命令输出删除尾随换行符中的至少一个(但是在许多shell ALL中)(并非所有的空格,特别是换行).所以上面的解决方案是有效的,除非你在参数的最后有换行符.那么你会丢失那些/那些换行符.修复很简单:在您的quote / esceval函数输出之前,在实际的命令值之后添加一个字符.顺便提一下,我们已经需要这样做,因为我们需要用单引号启动和停止转义参数.老实说,我不明白为什么没有做到这一点.你有两种选择:

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

这将确保参数已经完全转义,在构建最终字符串时不需要添加更多的单引号.这可能是您最接近的单一内联版本.如果你有一个sed依赖,你可以在这里停下来.

如果你不了解sed依赖,但是假设你的shell实际上是POSIX兼容的(还有一些在那里,特别是Solaris 10及更低版本的/ bin / sh)能够做这个下一个变体 – 但几乎所有的shell你需要关心将这样做很好):

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

您可能会注意到在这里看似冗长的引用:

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

这可以替换为:

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

我做前者的唯一原因是因为有一次有Bourne shell在将变量替换为引用的字符串时发生错误,其中变量的引用并没有完全开始,而是在变量替换所在的位置.因此,这是我的偏执的可移植习惯.在实践中,你可以做后者,这不会是一个问题.

如果您不想在shell环境的其余部分中破坏变量UnesCAPED,那么可以将该函数的整个内容包装到子shell中,如下所示:

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

“但等等”,你说:“我想在一个命令中对MULTIPLE参数做什么?如果我以任何原因从命令行运行它,我希望输出对于我来说看起来好像很清楚,“.

不要害怕,我有你的涵盖:

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

..或同样的事情,但与shell-only版本:

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中 – 我将它们分开,因为当您可以单独查看开始和结束单引号时,我觉得它使逻辑更加清晰打印语句.

附:还有这个我所做的怪物,这是一个polyfill,它会根据你的shell似乎能够支持必要的变量替换语法(之前的两个版本)之间进行选择(看起来很糟糕,因为只有shell版本必须是在一个被评估的字符串中,以防止不相容的shell在它们看到时被禁止):https://github.com/mentalisttraceur/esceval/blob/master/sh/esceval.sh

原文地址:https://www.jb51.cc/bash/383773.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐