为什么printf比echo好?


548

我听说printf比更好echo。从我的经验中,我只能回忆起一个必须使用的实例,printf因为它echo无法将某些文本输入RHEL 5.8上的某个程序,但是可以printf。但是显然还有其他差异,我想询问一下它们是什么,以及是否存在特定情况下何时使用一种对另一种。


一下echo -e
neverMind9

1
@ neverMind9 echo -eshbash(仅出于某种原因)不起作用,例如,shRUN
docker

echo -e对我来说,@CiprianTomoiagă 在Android Terminal中工作,从本质上讲是sh
neverMind9

Answers:


757

基本上,这是可移植性(和可靠性)问题。

最初,echo不接受任何选择,也没有扩展任何东西。它所做的只是输出其参数,该参数由空格分隔并由换行符终止。

现在,有人认为,如果我们可以做一些事情echo "\n\t"来输出换行符或制表符,或者可以选择不输出尾随的换行符,那将是很好的选择。

然后,他们更努力地思考,但没有将功能添加到外壳中(例如perl在双引号中\t实际上表示制表符),而是将其添加到中echo

大卫·科恩(David Korn)意识到了这个错误,并引入了一种新的shell引号形式:$'...'后来被抄袭了bashzsh但是到那时还为时已晚。

现在,当标准UNIX echo接收到包含两个字符\和的参数时t,而不是输出它们,而是输出一个制表符。并且一旦\c在参数中看到它,它就会停止输出(因此也不输出结尾的换行符)。

其他shell / Unix供应商/版本选择了不同的方法:他们添加了-e扩展转义序列的-n选项,以及不输出尾随换行符的选项。有些具有-E禁用转义序列的功能,有些具有-n但不具备-e,一种echo实现所支持的逸出序列的列表不一定与另一种实现所支持的相同。

Sven Mascheck的页面漂亮,可以显示问题的严重程度

在那些echo支持选项的实现中,通常不支持a --来标记选项的结束(echo某些非Bourne类shell 的内置功能,而zsh支持-该功能),例如,很难"-n"echoin 输出许多贝壳。

在诸如bash¹或ksh93²或yash$ECHO_STYLE变量)之类的某些shell上,其行为甚至取决于shell的编译方式或环境(echo如果$POSIXLY_CORRECT在环境中,并且版本为4zsh带有bsd_echo选项,则GNU 的行为也会改变),一些基于pdksh的posix选项及其是否被调用sh)。因此bash echo,即使来自相同版本的2,bash也不能保证表现相同。

POSIX说:如果第一个参数为,-n或者任何参数包含反斜杠,则行为未指定bash在这方面,echo不是POSIX,因为echo -e它没有-e<newline>按POSIX的要求输出。UNIX规范更加严格,它禁止-n并要求扩展某些转义序列,包括\c停止输出的转义序列。

鉴于许多实现不兼容,这些规范在这里并没有真正发挥作用。甚至某些经认证的系统(如macOS 5)也不兼容。

为了真正代表当前的现实,POSIX应该说如果第一个参数与^-([eEn]*|-help|-version)$扩展的regexp 匹配,或者任何参数包含反斜杠(或者其编码包含反斜杠字符编码的字符,例如α在使用BIG5字符集的语言环境中),则该行为是未指定。

总而言之,echo "$var"除非您确定$var不包含反斜杠字符且不以开头,否则您将不知道将输出什么-。POSIX规范实际上确实告诉我们printf在这种情况下可以使用。

因此,这意味着您不能echo用来显示不受控制的数据。换句话说,如果您正在编写脚本并且它正在接受外部输入(来自用户的自变量,或来自文件系统的文件名...),则无法使用echo它来显示它。

还行吧:

echo >&2 Invalid file.

这不是:

echo >&2 "Invalid file: $file"

(虽然在未以某种方式(例如在编译时或通过环境)启用该选项的情况下,对于某些(不符合UNIX的)echo实现(虽然可以正常使用),但是)。bashxpg_echo

file=$(echo "$var" | tr ' ' _)是不是在大多数实现OK(例外是yashECHO_STYLE=raw(与该警告yash的变量不能持有字节的任意序列,以便不任意文件名)和zshecho -E - "$var"6)。

printf另一方面,至少在仅限于的基本用法时更可靠echo

printf '%s\n' "$var"

将输出$var尾随换行符的内容,无论其可能包含什么字符。

printf '%s' "$var"

将输出它而没有尾随换行符。

现在,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' "$*"
)

可以避免将subshel​​l(在大多数shell实现中产生额外的进程)local IFS与许多shell一起使用,或者通过像下面这样编写来避免:

echo() {
  if [ "$#" -gt 0 ]; then
     printf %s "$1"
     shift
  fi
  if [ "$#" -gt 0 ]; then
     printf ' %s' "$@"
  fi
  printf '\n'
}

笔记

1.怎样bashecho行为可以被改变。

使用bash,在运行时,有两件事可以控制echo(在旁边enable -n echo或重新定义echo为函数或别名)其行为:xpg_echo bash选项和是否bash处于posix模式。posix如果模式可以启用bash被称为sh或者POSIXLY_CORRECT是在环境或与该posix选项:

大多数系统上的默认行为:

$ bash -c 'echo -n "\0101"'
\0101% # the % here denotes the absence of newline character

xpg_echo 根据UNIX要求扩展序列:

$ BASHOPTS=xpg_echo bash -c 'echo "\0101"'
A

它仍然很荣幸-n-e(和-E):

$ BASHOPTS=xpg_echo bash -c 'echo -n "\0101"'
A%

使用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

这次bash是同时符合POSIX和UNIX。请注意,在POSIX模式下,bash它仍然不符合POSIX,因为它不会-e在以下位置输出:

$ env SHELLOPTS=posix bash -c 'echo -e'

$

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中不是这种情况)。

2.怎样ksh93echo行为可以被改变。

在中ksh93,是否echo扩展转义序列以及是否识别选项取决于$PATHand和/或$_AST_FEATURES环境变量的内容。

如果$PATH包含的组件包含/5bin/xpg之前/bin/usr/bin之后,则它将以SysV / UNIX方式运行(扩展序列,不接受选项)。如果找到,/ucb或者/bsd首先找到,或者如果$_AST_FEATURES7包含UNIVERSE = ucb,那么它将以BSD 3的方式运行(-e启用扩展,识别-n)。

默认值是系统相关的,Debian上的BSD(请参阅builtin getconf; getconf UNIVERSE最新版本的ksh93 的输出):

$ 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

3. BSD用于echo -e吗?

在此,对BSD的处理-e选项参考有些误导。这些差异和不兼容echo行为中的大多数都是在AT&T中引入的:

  • \n\0ooo\c在程序员工作台UNIX(基于UNIX V6),其余的(\b\r在unix系统III ...)参考
  • -n在Unix V7中(作者Dennis Ritchie Ref
  • -e在Unix V8中(作者Dennis Ritchie Ref
  • -E本身可能最初来自bash(1.13.5 版中的 CWRU / CWRU.chlog 提到Brian Fox在1992-10-18上添加了它,GNU echo在10天后发布的sh-utils-1.8中不久将其复制了)

echo在内置的sh或BSD系统都支持-e,因为他们开始使用它的Almquist外壳在90年代初的一天,独立的echo效用,这一天不支持它在那里(FreeBSD的echo仍然不支持-e,虽然它不支持-n像Unix V7(并且也\c只能在最后一个参数的末尾)。

的处理-e加入ksh93echo时候在BSD 宇宙在2006年发布和ksh93r版本可以在编译时被禁用。

4. GNU echo在8.31中的行为变化

由于coreutils的8.31(和这次提交),GNU echo现在扩大逃脱默认序列时POSIXLY_CORRECT的环境,以配合的行为bash -o posix -O xpg_echoecho内置(见bug报告)。

5. macOS 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中的一个,如果第一个参数为-n或(自1995年起),如果最后一个参数以结束\c,则禁止换行输出,但不支持UNIX所需的任何其他反斜杠序列,甚至不支持\\

6. echo可以逐字输出任意数据的实现

严格来说,您还可以指望/bin/echo上面的FreeBSD / macOS (而不是它们的shell的echo内置文件)可以在其中写入zshs echo -E - "$var"yashs ECHO_STYLE=raw echo "$var"printf '%s\n' "$var"):

/bin/echo "$var
\c"

支持-E-n(或可以配置为)的实现还可以执行以下操作:

echo -nE "$var
"

zshecho -nE - "$var"printf %s "$var")可以写成

/bin/echo "$var\c"

7. _AST_FEATURES和ASTUNIVERSE

所述_AST_FEATURES不意味着进行直接操作,它被用于跨命令执行传播AST配置设置。该配置应通过(未记录)astgetconf()API 来完成。在内部ksh93getconf内置(通过启用builtin getconf或通过调用启用command /opt/ast/bin/getconf)是到的接口。astgetconf()

例如,您builtin getconf; getconf UNIVERSE = att可以将UNIVERSE设置更改为attecho除其他因素外,还可以使SysV行为)。完成此操作后,您会注意到$_AST_FEATURES环境变量contains UNIVERSE = att


13
许多早期的unix开发都是孤立发生的,并且没有应用良好的软件工程原理,例如 “当您更改界面时,更改名称”
Henk Langeveld 2014年

7
请注意,作为引号语法的一部分,echo扩展\x序列而不是外壳的一个(也是唯一的)好处是您可以输出一个NUL字节(Unix的另一个错误设计是用空定界的)串,其中所述系统调用的一半(像execve())不能把字节)任意序列
斯特凡Chazelas

正如您可能在Sven Maschek的网页上看到的那样,printf由于大多数实现都做错了,所以这似乎也是一个问题。我知道的唯一正确的实现是,boshSven Maschek的页面未通过列出空字节的问题\0
schily

1
这是一个很好的答案-感谢@StéphaneChazelas编写了它。
JoshuaRLi

28

您可能要使用printf其格式选项。echo在打印变量或(简单)行的值时很有用,但仅此而已。printf基本上可以完成C版的功能。

用法和功能示例:

Echo

echo "*** Backup shell script ***"
echo
echo "Runtime: $(date) @ $(hostname)"
echo

printf

vech="bike"
printf "%s\n" "$vech"

资料来源:


@ 0xC0000022L我的立场是正确的,谢谢。我没有注意到我匆忙回答问题时链接到错误的网站。感谢您的贡献和纠正。
NlightNFotis

7
如果echo变量的值包含元字符,则用于打印变量可能会失败。
基思·汤普森

17

如果您想称呼它为“优势”,那就是您不必告诉它echo来解释某些转义序列,例如\n。它知道要解释它们,不需-e要这样做。

printf "some\nmulti-lined\ntext\n"

(注意:除非您提供选择,否则最后一个\n是必要的,echo暗含它-n

echo -e "some\nmulti-lined\ntext"

注意最后\nprintf。最终,您所使用的内容取决于口味和要求echoprintf


1
适用/usr/bin/echobash内置。的dashksh并且zsh内置echo不需要-e开关扩大反斜杠转义字符。
manatwork

为什么要害怕报价?您的措辞暗示它不一定是真正的优势。
基思·汤普森

2
@KeithThompson:实际上,它们只意味着不是每个人都可以认为这是一种优势。
0xC0000022L 2013年

你能详细谈谈?为什么不是优势?短语“如果您想称其为”则强烈暗示您认为并非如此。
基思·汤普森

2
或者你可以做printf '%s\n' 'foo\bar
nyuszika7h 2014年

6

缺点之一printf是性能,因为内置外壳echo要快得多。这在Cygwin中尤其重要,因为新命令的每个实例都会导致大量Windows开销。当我将重用echo程序从使用/bin/echo更改为shell的echo时,性能几乎翻了一番。在可移植性和性能之间进行权衡。永远不要灌输灌篮printf


15
printf如今已在大多数外壳中构建(bash,dash,ksh,zsh,yash,一些pdksh衍生物...因此也包括通常在cygwin上发现的外壳)。唯一值得注意的例外是某些pdksh派生类。
斯特凡Chazelas

但是,其中许多printf实现都已被破坏。当您想用来printf输出nul字节时,这是必不可少的,但是某些实现即使不应该以格式字符串解释“ \ c”。
schily

2
@schily,printf如果您使用\c格式字符串,则POSIX不会指定的行为,因此printf实现可以在这方面做任何想要的事情。例如,有些人将其与PWB中的处理相同echo(导致printf退出),ksh将其用作\cA控制字符(用于printf格式参数,$'...'但不用于for echoprint!)。不知道这与打印NUL字节有什么关系,或者您指ksh的是printf '\c@'
斯特凡Chazelas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.