amp*_*ent 635 echo text-processing printf
我听说printf比echo. 根据我的经验,我只能回忆起一个我不得不使用的实例,printf因为它echo无法将一些文本输入到 RHEL 5.8 上的某个程序中,但printf确实如此。但显然,还有其他差异,我想询问它们是什么以及是否有特定情况下何时使用一种与另一种。
Sté*_*las 879
基本上,这是一个可移植性(和可靠性)问题。
最初,echo没有接受任何选项,也没有扩展任何内容。它所做的只是输出由空格字符分隔并以换行符终止的参数。
现在,有人认为如果我们可以做一些诸如echo "\n\t"输出换行符或制表符之类的事情,或者可以选择不输出尾随的换行符,那就太好了。
然后他们更努力地思考,但没有将该功能添加到 shell(例如perl双引号内的位置,\t实际上表示制表符),而是将其添加到echo.
David Korn 意识到了这个错误并引入了一种新形式的 shell 引号:$'...'后来被bashand复制了,zsh但那时已经太晚了。
现在,当一个标准的UNIXecho接收包含两个字符的参数\和t,代替它们输出时,它输出一个制表符。一旦它\c在参数中看到,它就会停止输出(因此也不输出尾随的换行符)。
其他 shell/Unix 供应商/版本选择了不同的方式:他们添加了一个-e选项来扩展转义序列,以及一个-n不输出尾随换行符的选项。有些有-E禁用转义序列,有些有-n但没有-e,一个echo实现支持的转义序列列表不一定与另一个支持的相同。
Sven Mascheck 有一个很好的页面,显示了问题的严重程度。
在那些echo支持选项的实现上,通常不支持 a--来标记选项的结束(echo一些非 Bourne-like shell的内置支持-,但zsh 支持),因此例如,很难"-n"用echoin输出许多贝壳。
在某些 shell 上,如bash¹ 或ksh93² 或yash($ECHO_STYLE变量),行为甚至取决于 shell 的编译方式或环境(GNUecho的行为也会改变,如果$POSIXLY_CORRECT在环境中并且在版本4 中,zsh带有它的bsd_echo选项,一些基于 pdksh 的posix选项或它们是否被称为sh)。因此bash echo,即使来自相同版本的两个sbash也不能保证行为相同。
POSIX 说:如果第一个参数是-n或任何参数包含反斜杠,则行为是 unspecified。bash在这方面 echo 不是 POSIX,因为例如echo -e没有-e<newline>按照 POSIX 要求输出。UNIX 规范更严格,它禁止-n并要求扩展一些转义序列,包括\c停止输出的转义序列。
鉴于许多实现不合规,这些规范在这里并没有真正起到作用。甚至一些经过认证的系统(如 macOS 5)也不符合要求。
为了真正代表当前的现实,POSIX 实际上应该说:如果第一个参数与^-([eEn]*|-help|-version)$扩展的正则表达式匹配,或者任何参数包含反斜杠(或?在使用 BIG5 字符集的语言环境中,其编码包含反斜杠字符编码的字符),则行为是未指定。
总而言之,echo "$var"除非您可以确保$var不包含反斜杠字符并且不以-. POSIX 规范实际上确实告诉我们printf在这种情况下使用。
所以这意味着你不能echo用来显示不受控制的数据。换句话说,如果您正在编写一个脚本并且它接受外部输入(来自用户作为参数,或来自文件系统的文件名......),则不能用于echo显示它。
还行吧:
echo >&2 Invalid file.
Run Code Online (Sandbox Code Playgroud)
这不是:
echo >&2 "Invalid file: $file"
Run Code Online (Sandbox Code Playgroud)
(尽管它可以在某些(非 UNIX 兼容的)echo实现中正常工作,例如bash's 当该xpg_echo选项没有以一种或另一种方式启用时,例如在编译时或通过环境)。
file=$(echo "$var" | tr ' ' _)在大多数实现中都不好(例外是yashwith ECHO_STYLE=raw(注意yash's 的变量不能包含任意字节序列,因此不能包含任意文件名)和zsh's echo -E - "$var"6)。
printf,另一方面更可靠,至少当它仅限于echo.
printf '%s\n' "$var"
Run Code Online (Sandbox Code Playgroud)
$var无论它可能包含什么字符,都将输出后跟换行符的内容。
printf '%s' "$var"
Run Code Online (Sandbox Code Playgroud)
将在没有尾随换行符的情况下输出它。
现在,printf实现之间也存在差异。POSIX 指定了一个核心功能,但还有很多扩展。例如,有些支持 a%q来引用参数,但它的完成方式因外壳而异,有些支持\uxxxxunicode 字符。printf '%10s\n' "$var"在多字节语言环境中,行为因人而异,至少有三种不同的结果printf %b '\123'
但最终,如果您坚持使用 POSIX 功能集printf并且不尝试用它做任何太花哨的事情,那么您就摆脱了麻烦。
但请记住,第一个参数是格式,因此不应包含可变/不受控制的数据。
echo可以使用 实现更可靠的方法printf,例如:
echo() ( # subshell for local scope for $IFS
IFS=" " # needed for "$*"
printf '%s\n' "$*"
)
echo_n() (
IFS=" "
printf %s "$*"
)
echo_e() (
IFS=" "
printf '%b\n' "$*"
)
Run Code Online (Sandbox Code Playgroud)
子shell(这意味着在大多数shell实现中会产生一个额外的进程)可以避免local IFS与许多shell一起使用,或者像这样写:
echo() {
if [ "$#" -gt 0 ]; then
printf %s "$1"
shift
if [ "$#" -gt 0 ]; then
printf ' %s' "$@"
fi
fi
printf '\n'
}
Run Code Online (Sandbox Code Playgroud)
bash的echo行为可以被改变。使用bash,在运行时,有两件事可以控制echo(除了enable -n echo或重新定义echo为函数或别名)的行为:xpg_echo bash选项和是否bash处于 posix 模式。posix如果bash被称为 assh或 ifPOSIXLY_CORRECT在环境中或使用以下posix选项,则可以启用模式:
大多数系统上的默认行为:
$ bash -c 'echo -n "\0101"'
\0101% # the % here denotes the absence of newline character
Run Code Online (Sandbox Code Playgroud)
xpg_echo 按照 UNIX 的要求扩展序列:
$ BASHOPTS=xpg_echo bash -c 'echo "\0101"'
A
Run Code Online (Sandbox Code Playgroud)
它仍然尊重-n和-e(和-E):
$ BASHOPTS=xpg_echo bash -c 'echo -n "\0101"'
A%
Run Code Online (Sandbox Code Playgroud)
随着xpg_echo与POSIX模式:
$ env BASHOPTS=xpg_echo POSIXLY_CORRECT=1 bash -c 'echo -n "\0101"'
-n A
$ env BASHOPTS=xpg_echo sh -c 'echo -n "\0101"' # (where sh is a symlink to bash)
-n A
$ env BASHOPTS=xpg_echo SHELLOPTS=posix bash -c 'echo -n "\0101"'
-n A
Run Code Online (Sandbox Code Playgroud)
这一次,bash既符合 POSIX 又符合 UNIX。请注意,在 POSIX 模式下,bash仍然不符合 POSIX 标准,因为它不输出-e:
$ env SHELLOPTS=posix bash -c 'echo -e'
$
Run Code Online (Sandbox Code Playgroud)
xpg_echo 和 posix 的默认值可以在编译时使用脚本的--enable-xpg-echo-default和--enable-strict-posix-default选项定义configure。这通常是最新版本的 OS/X 在构建他们的/bin/sh. 在他们的脑子没有任何Unix / Linux实现/分布通常会做那。实际上,事实并非如此,/bin/bash虽然/bin/bashOracle 随 Solaris 11(在可选软件包中)一起提供的似乎是构建的--enable-xpg-echo-default(在 Solaris 10 中并非如此)。
ksh93的echo行为可以被改变。在 中ksh93,是否echo扩展转义序列和识别选项取决于$PATH和/或$_AST_FEATURES环境变量的内容。
如果$PATH包含一个包含组件/5bin或/xpg在之前/bin或/usr/bin组件,然后它的行为的SysV / UNIX方式(扩展序列,不接受选项)。如果它找到/ucbor /bsdfirst 或者如果$_AST_FEATURES7 contains UNIVERSE = ucb,那么它的行为是 BSD 3方式(-e启用扩展,识别-n)。
默认是依赖于系统的,Debian 上的 BSD(参见builtin getconf; getconf UNIVERSEksh93 最新版本的输出):
$ ksh93 -c 'echo -n' # default -> BSD (on Debian)
$ PATH=/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /xpg before /bin or /usr/bin -> XPG
-n
$ PATH=/5binary:$PATH ksh93 -c 'echo -n' # /5bin before /bin or /usr/bin -> XPG
-n
$ PATH=/5binary:$PATH _AST_FEATURES='UNIVERSE = ucb' ksh93 -c 'echo -n' # -> BSD
$ PATH=/ucb:/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /ucb first -> BSD
$ PATH=/bin:/foo/xpgbar:$PATH ksh93 -c 'echo -n' # /bin before /xpg -> default -> BSD
Run Code Online (Sandbox Code Playgroud)
对 BSD 的-e选项处理的引用在这里有点误导。大多数这些不同且不兼容的echo行为都是在 AT&T 引入的:
\n, \0ooo,\c在 Programmer's Work Bench UNIX(基于 Unix V6)中,其余 ( \b, \r...) 在 Unix System III Ref 中。-n在 Unix V7 中(由 Dennis Ritchie参考)-e在 Unix V8 中(由 Dennis Ritchie参考)-E本身可能最初来自bash(1.13.5版中的CWRU/CWRU.chlog提到 Brian Fox 在 1992-10-18 添加它,GNUecho在 10 天后发布的 sh-utils-1.8 不久后复制它)虽然echo的内置shBSD系统都支持-e,因为他们开始使用它的Almquist外壳在90年代初的一天,独立的echo效用,这一天不支持它在那里(FreeBSD的echo仍然不支持-e,虽然它不支持-n像Unix V7(也\c只是在最后一个参数的末尾))。
在 2006 年发布的 ksh93r 版本的 BSD世界中,的处理-e被添加到ksh93's echo,并且可以在编译时禁用。
从 coreutils 8.31(和这次提交)开始,echo当 POSIXLY_CORRECT 在环境中时,GNU现在默认扩展转义序列,以匹配bash -o posix -O xpg_echo的echo内置行为(参见错误报告)。
echo大多数版本的 macOS 都获得了 OpenGroup 的 UNIX 认证。
他们的sh内置程序echo是兼容的,因为它bash(一个非常旧的版本)xpg_echo默认启用,但他们的独立echo实用程序不是。env echo -n什么都不输出而不是-n<newline>,env echo '\n'输出\n<newline>而不是<newline><newline>。
这/bin/echo是来自 FreeBSD 的那个,如果第一个参数是-nor (自 1995 年以来),如果最后一个参数以 结尾\c,则抑制换行符输出,但不支持 UNIX 所需的任何其他反斜杠序列,甚至不支持\\。
echo可以逐字输出任意数据的实现严格来说,您还可以计算/bin/echo上面的FreeBSD/macOS (不是它们的 shellecho内置),其中zsh'secho -E - "$var"或yash's ECHO_STYLE=raw echo "$var"( printf '%s\n' "$var") 可以写成:
/bin/echo "$var
\c"
Run Code Online (Sandbox Code Playgroud)
和zsh的echo -nE - "$var"(printf %s "$var")可以写成
/bin/echo "$var\c"
Run Code Online (Sandbox Code Playgroud)
支持-E和-n(或可以配置为)的实现也可以:
echo -nE "$var
"
Run Code Online (Sandbox Code Playgroud)
对于相当于printf '%s\n' "$var".
_AST_FEATURES和 ASTUNIVERSE所述_AST_FEATURES不意味着进行直接操作,它被用于跨命令执行传播AST配置设置。配置旨在通过(未记录的)astgetconf()API 完成。在内部ksh93,getconf内置(builtin getconf通过调用或启用command /opt/ast/bin/getconf)是astgetconf()
例如,您builtin getconf; getconf UNIVERSE = att可以将UNIVERSE设置更改为att(导致echo以 SysV 方式执行其他操作)。这样做之后,您会注意到$_AST_FEATURES环境变量包含UNIVERSE = att.
Nli*_*tis 33
您可能想要使用printf其格式选项。echo在打印变量或(简单)行的值时很有用,但仅此而已。printf基本上可以做它的C版本可以做的事情。
示例用法和功能:
Echo:echo "*** Backup shell script ***"
echo
echo "Runtime: $(date) @ $(hostname)"
echo
Run Code Online (Sandbox Code Playgroud)
printf:vech="bike"
printf "%s\n" "$vech"
Run Code Online (Sandbox Code Playgroud)
资料来源:
0xC*_*22L 20
一个“优势”,如果你想这样称呼它,就是你不必告诉它像echo解释某些转义序列,如\n. 它知道解释它们并且不需-e要这样做。
printf "some\nmulti-lined\ntext\n"
Run Code Online (Sandbox Code Playgroud)
(注意:最后一个\n是必要的,echo暗示它,除非您提供-n选项)
相对
echo -e "some\nmulti-lined\ntext"
Run Code Online (Sandbox Code Playgroud)
注意最后\n在printf。归根结底,这取决于您使用的口味和要求:echo或printf.
小智 5
一个缺点printf是性能,因为内置外壳echo要快得多。这尤其在 Cygwin 中发挥作用,其中新命令的每个实例都会导致大量的 Windows 开销。当我将我的 echo-heavy 程序从 using/bin/echo更改为 shell 的 echo 时,性能几乎翻了一番。这是便携性和性能之间的权衡。总是使用printf.