前言。我知道在该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
如何获取C
Bash 中的行为?谢谢。
编辑:
我对这种行为感到好奇,我无法接受改变参数顺序的解决方法。它应该只使用格式字符串就可以工作。
编辑:
例如,考虑 GNU Bash 源代码国际化。如果没有这个功能,这是非常不可能的。
答案1
你不能与bash
.这printf
实用程序的 POSIX 规范也不支持。您必须手动重新排序参数。
printf
和 的(或print -f
) 内置函数支持ksh93
它们zsh
:
$ printf '%2$s%1$s\n' a b
ba
GNUawk
或perl
也支持它,所以如果您安装了其中任何一个,在 中bash
,您可以重新定义printf
为如下函数:
printf() { zsh -c '"$0" "$@"' printf "$@"; }
或者:
printf() { ksh93 -c '"$0" "$@"' printf "$@"; }
gawk
使用or会花费更多的精力,perl
因为gawk
不会让您ARGV
按原样传递,gawk
也perl
不会扩展\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 将编号和未编号转换说明符的混合描述为未定义,我认为这是合理的。