在 /usr/bin/printf 或 Bash printf 中按指定顺序处理参数

在 /usr/bin/printf 或 Bash printf 中按指定顺序处理参数

前言。我知道在该C语言中,该printf函数允许执行以下操作:

printf('%2$s %2$s %1$s %1$s', 'World', 'Hello');

输出:Hello Hello World World

但在 GNU Bash 中似乎不支持此功能:

printf '%2$s %2$s %1$s %1$s' 'World' 'Hello'

输出:bash: printf: $': invalid format character

我还尝试使用本地/usr/bin/printf

/usr/bin/printf '%2$s %2$s %1$s %1$s' 'World' 'Hello'

输出:/usr/bin/printf: %2$: invalid conversion specification

如何获取CBash 中的行为?谢谢。

编辑:

我对这种行为感到好奇,我无法接受改变参数顺序的解决方法。它应该只使用格式字符串就可以工作。

编辑:

例如,考虑 GNU Bash 源代码国际化。如果没有这个功能,这是非常不可能的。

答案1

你不能与bash.这printf实用程序的 POSIX 规范也不支持。您必须手动重新排序参数。

printf和 的(或print -f) 内置函数支持ksh93它们zsh

$ printf '%2$s%1$s\n' a b
ba

GNUawkperl也支持它,所以如果您安装了其中任何一个,在 中bash,您可以重新定义printf为如下函数:

printf() { zsh -c '"$0" "$@"' printf "$@"; }

或者:

printf() { ksh93 -c '"$0" "$@"' printf "$@"; }

gawk使用or会花费更多的精力,perl因为gawk不会让您ARGV按原样传递,gawkperl不会扩展\x序列(除非在双引号中的代码中逐字传递),并且它们不支持(用于模拟的实用程序%b的扩展)printf系统V echo)。

答案2

您可以通过将 printf 重新定义为函数,使 bash 无需调用其他进程即可完成此操作:

printf() {  # add support for numbered conversion specifiers
    local -a args
    local opt=
    case $1 in
    -v) opt=-v$2; shift 2;;
    -*) opt=$1; shift;;
    esac
    local format=$1; shift
    while [[ $format =~ ((^|.*[^%])%)([0-9]+)\$(.*) ]]
    do
        args=("${!BASH_REMATCH[3]}" "${args[@]}")
        format=${BASH_REMATCH[1]}${BASH_REMATCH[4]}
    done
    let ${#args[@]} && set -- "${args[@]}"
    builtin printf $opt "$format" "$@"
}

这是通过将格式字符串中的编号转换说明符替换为未编号的转换说明符,同时使用数字为修改后的参数列表选择参数来实现的。如果不存在编号转换说明符,则 printf 内置函数将按原样查看函数的参数。应该是一个简单的替代品,几乎没有什么令人惊讶的行为。

构建新参数列表的语句中的顺序可能令人惊讶,因为 bash 正则表达式匹配是贪婪的,所以需要将新参数推到 args[] 数组的前面而不是追加;每次 while 循环时,它找到的编号转换说明符将是格式字符串中剩余的最后一个。

另请注意,当传递的参数多于包含转换说明符的格式字符串时,内置 printf 通常执行的格式字符串回收在存在编号转换说明符时不会发生;与编号说明符不对应的参数将被简单地忽略。鉴于 POSIX 将编号和未编号转换说明符的混合描述为未定义,我认为这是合理的。

相关内容