我听说printf
比 更好echo
。我只能回忆起我的经历中的一个实例,当时我必须使用,printf
因为echo
无法将一些文本输入到 RHEL 5.8 上的某个程序中,但printf
我确实使用了。但显然,还有其他差异,我想询问它们是什么,以及是否存在特定情况下使用其中一种与另一种的情况。
答案1
基本上,这是一个可移植性(和可靠性)问题。
最初,echo
没有接受任何选项,也没有扩展任何内容。它所做的只是输出由空格字符分隔并由换行符终止的参数。
echo "\n\t"
现在,有人认为如果我们可以执行诸如输出换行符或制表符之类的操作,或者可以选择不输出尾随换行符,那就太好了。
然后他们更努力地思考,但他们没有将该功能添加到 shell(例如perl
双引号内的位置,\t
实际上意味着制表符),而是将其添加到echo
.
David Korn 意识到了这个错误,并引入了一种新形式的 shell 引用:$'...'
后来被bash
and复制,zsh
但那时已经太晚了。
现在,当标准 UNIXecho
接收到包含两个字符\
和 的参数时t
,它不会输出它们,而是输出一个制表符。一旦它看到\c
参数,它就会停止输出(因此尾随换行符也不会输出)。
其他 shell/Unix 供应商/版本选择了不同的做法:他们添加了一个-e
扩展转义序列的选项,以及一个-n
不输出尾随换行符的选项。有些有 a-E
来禁用转义序列,有些有-n
但没有-e
,并且一种实现支持的转义序列列表echo
不一定与另一种实现支持的相同。
斯文·马斯切克有一个很好的页面,显示了问题的严重程度。
在那些echo
支持选项的实现上,通常不支持 a 来--
标记选项的结尾(echo
一些非 Bourne-like shell 和 toybox echo
(Android 的独立echo
实用程序)的内置功能是这样做的,但 zsh 支持-
这一点),例如,"-n"
在echo
很多 shell 中很难输出。
在某些 shell 上,如bash
¹ 或ksh93
² 或yash
($ECHO_STYLE
变量),行为甚至取决于 shell 的编译方式或环境(如果在环境中并且使用版本4 , 's 及其选项, GNUecho
的行为也会改变,一些基于 pdksh 的选项或它们是否被称为)。因此,即使来自同一版本的两个s 也不能保证行为相同。$POSIXLY_CORRECT
zsh
bsd_echo
posix
sh
bash
echo
bash
POSIX 说:如果第一个参数是-n
或任何参数包含反斜杠,则行为未指定。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.
这不是:
echo >&2 "Invalid file: $file"
(尽管它可以在某些(不兼容 UNIX 的)echo
实现中正常工作,例如bash
当xpg_echo
选项尚未以一种或另一种方式(例如在编译时或通过环境)启用时。
file=$(echo "$var" | tr ' ' _)
在大多数实现中都不好(例外是yash
with ECHO_STYLE=raw
(需要注意的是yash
's 变量不能保存任意字节序列,因此不能保存任意文件名)和zsh
's echo -E - "$var"
6)。
printf
另一方面,更可靠,至少当它仅限于 的基本用法时echo
。
printf '%s\n' "$var"
$var
将输出后跟换行符的内容,无论它可能包含什么字符。
printf '%s' "$var"
输出时不带尾随换行符。
printf
现在,实现之间也存在差异。 POSIX 指定了核心功能,但还有很多扩展。例如,有些支持 a%q
来引用参数,但其完成方式因 shell 而异,有些支持\uxxxx
Unicode 字符。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' "$*"
)
子 shell(这意味着在大多数 shell 实现中生成一个额外的进程)可以避免local IFS
与许多 shell 一起使用,或者通过这样编写:
echo() {
if [ "$#" -gt 0 ]; then
printf %s "$1"
shift
if [ "$#" -gt 0 ]; then
printf ' %s' "$@"
fi
fi
printf '\n'
}
在ksh88和pdksh及其一些衍生产品中,printf
不是内置的。在那里,您可能更喜欢使用print -r --
(for echo
) 和print -rn --
(for echo -n
/ \c
) 来打印它们的参数,以空格分隔(后面跟一个换行符,不带-n
),无需更改(也适用于zsh
)。
笔记
1. 如何bash
改变echo
行为。
对于,在运行时,有两件事可以控制(除了或重新定义为函数或别名)bash
的行为:选项以及是否处于 posix 模式。如果被调用为或如果在环境中或使用以下选项,则可以启用模式:echo
enable -n echo
echo
xpg_echo
bash
bash
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'
$
--enable-xpg-echo-default
xpg_echo 和 posix 的默认值可以在编译时使用脚本的和--enable-strict-posix-default
选项定义configure
。这通常是最新版本的 OS/X 构建其/bin/sh
.不过,头脑清醒的 Unix/Linux 实现/发行版通常不会这样做。实际上,事实并非如此,/bin/bash
/bin/bash
Oracle 随 Solaris 11(在可选包中)附带的 似乎是使用 Solaris 10 构建的--enable-xpg-echo-default
(Solaris 10 中并非如此)。
2. 如何ksh93
改变echo
行为
在 中ksh93
,是否扩展转义序列并识别选项取决于和/或环境变量echo
的内容。$PATH
$_AST_FEATURES
如果$PATH
包含的组件包含/5bin
或/xpg
之前的/bin
或/usr/bin
组件,则其行为为 SysV/UNIX 方式(扩展序列,不接受选项)。如果它首先找到/ucb
or/bsd
或 如果$_AST_FEATURES
7包含UNIVERSE = ucb
,那么它会以 BSD 3方式运行(-e
以启用扩展、识别-n
)。
builtin getconf; getconf UNIVERSE
默认值取决于系统,Debian 上的 BSD(请参阅最新版本的 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 for echo -e?
这里引用 BSD 来处理该-e
选项有点误导。大多数这些不同且不兼容的echo
行为都是 AT&T 引入的:
\n
,\0ooo
,\c
在程序员工作台 UNIX(基于 Unix V6)中,其余的 (\b
,\r
...) 在 Unix System III 中参考号。-n
在 Unix V7 中(丹尼斯·里奇参考号)-e
在 Unix V8 中(丹尼斯·里奇参考号)-E
它本身可能最初来自bash
(CWRU/CWRU.chlog in版本1.13.5提到 Brian Fox 在 1992 年 10 月 18 日添加了它,GNUecho
在 10 天后发布的 sh-utils-1.8 中不久复制了它)
虽然BSDecho
的内置程序自 90 年代初开始使用 Almquist shell 以来就sh
一直支持它,但迄今为止的独立实用程序还不支持它(-e
echo
自由BSDecho
仍然不支持,尽管它确实像 Unix V7 一样-e
支持(也但仅在最后一个参数的末尾))。-n
\c
在 BSD 中,对 的处理-e
被添加到ksh93
s中echo
宇宙在 2006 年发布的 ksh93r 版本中,可以在编译时禁用。
4. GNU echo 8.31 中的行为变化
从 coreutils 8.31 开始(以及这次提交),echo
当环境中存在 POSIXLY_CORRECT 时,GNU 现在默认扩展转义序列,以匹配 的内置函数的行为bash -o posix -O xpg_echo
(echo
请参阅错误报告)。
5.苹果系统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
or (自 1995 年起),如果最后一个参数以 结尾\c
,则抑制换行符输出,但不支持 UNIX 所需的任何其他反斜杠序列,甚至不支持\\
。
6.echo
可以逐字输出任意数据的实现
严格来说,你也可以算上/bin/echo
上面的 FreeBSD/macOS(不是它们echo
内置的 shell),其中zsh
secho -E - "$var"
或yash
s ECHO_STYLE=raw echo "$var"
( printf '%s\n' "$var"
) 可以这样写:
/bin/echo "$var
\c"
并且zsh
( echo -nE - "$var"
)printf %s "$var"
可以写成
/bin/echo "$var\c"
支持-E
和-n
(或可以配置为)的实现还可以执行以下操作:
echo -nE "$var
"
对于相当于printf '%s\n' "$var"
.
7._AST_FEATURES
和ASTUNIVERSE
它_AST_FEATURES
并不意味着可以直接操作,它用于在命令执行中传播 AST 配置设置。配置是通过(未记录的)API 完成的astgetconf()
。在内部ksh93
,getconf
内置函数(builtin getconf
通过调用或启用command /opt/ast/bin/getconf
)是接口astgetconf()
例如,您需要将设置builtin getconf; getconf UNIVERSE = att
更改UNIVERSE
为att
(导致echo
以 SysV 方式运行)。执行此操作后,您会注意到$_AST_FEATURES
环境变量包含UNIVERSE = att
.
答案2
您可能想使用printf
它的格式选项。echo
当打印变量或(简单)行的值时很有用,但仅此而已。printf
基本上可以做C版本能做的事情。
用法和功能示例:
Echo
:
echo "*** Backup shell script ***"
echo
echo "Runtime: $(date) @ $(hostname)"
echo
printf
:
vech="bike"
printf "%s\n" "$vech"
资料来源:
答案3
答案4
其缺点之一printf
是性能,因为内置 shell 速度echo
要快得多。这在 Cygwin 中尤其重要,因为新命令的每个实例都会导致大量的 Windows 开销。当我将使用重回声的程序更改/bin/echo
为 shell 的回声时,性能几乎翻了一番。这是可移植性和性能之间的权衡。总是使用并不是一劳永逸的printf
。