Echo:

Echo:

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

答案1

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

最初,echo没有接受任何选项,也没有扩展任何内容。它所做的只是输出由空格字符分隔并由换行符终止的参数。

echo "\n\t"现在,有人认为如果我们可以执行诸如输出换行符或制表符之类的操作,或者可以选择不输出尾随换行符,那就太好了。

然后他们更努力地思考,但他们没有将该功能添加到 shell(例如perl双引号内的位置,\t实际上意味着制表符),而是将其添加到echo.

David Korn 意识到了这个错误,并引入了一种新形式的 shell 引用:$'...'后来被bashand复制,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_CORRECTzshbsd_echoposixshbash echobash

POSIX 说:如果第一个参数是-n或任何参数包含反斜杠,则行为未指定bashecho 在这方面不是 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实现中正常工作,例如bashxpg_echo选项尚未以一种或另一种方式(例如在编译时或通过环境)启用时。

file=$(echo "$var" | tr ' ' _)在大多数实现中都不好(例外是yashwith 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 而异,有些支持\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' "$*"
)

子 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 模式。如果被调用为或如果在环境中或使用以下选项,则可以启用模式:echoenable -n echoechoxpg_echo bashbashposixbashshPOSIXLY_CORRECTposix

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

$ 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_echoPOSIX 模式:

$ 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-defaultxpg_echo 和 posix 的默认值可以在编译时使用脚本的和--enable-strict-posix-default选项定义configure。这通常是最新版本的 OS/X 构建其/bin/sh.不过,头脑清醒的 Unix/Linux 实现/发行版通常不会这样做/bin/bash。实际上,事实并非如此,/bin/bashOracle 随 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 方式(扩展序列,不接受选项)。如果它首先找到/ucbor/bsd或 如果$_AST_FEATURES7包含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一直支持它,但迄今为止的独立实用程序还不支持它(-eecho自由BSDecho仍然不支持,尽管它确实像 Unix V7 一样-e支持(也但仅在最后一个参数的末尾))。-n\c

在 BSD 中,对 的处理-e被添加到ksh93s中echo宇宙在 2006 年发布的 ksh93r 版本中,可以在编译时禁用。

4. GNU echo 8.31 中的行为变化

从 coreutils 8.31 开始(以及这次提交),echo当环境中存在 POSIXLY_CORRECT 时,GNU 现在默认扩展转义序列,以匹配 的内置函数的行为bash -o posix -O xpg_echoecho请参阅错误报告)。

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 中的一个,如果第一个参数是-nor (自 1995 年起),如果最后一个参数以 结尾\c,则抑制换行符输出,但不支持 UNIX 所需的任何其他反斜杠序列,甚至不支持\\

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

严格来说,你也可以算上/bin/echo上面的 FreeBSD/macOS(不是它们echo内置的 shell),其中zshsecho -E - "$var"yashs 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()。在内部ksh93getconf内置函数(builtin getconf通过调用或启用command /opt/ast/bin/getconf)是接口astgetconf()

例如,您需要将设置builtin getconf; getconf UNIVERSE = att更改UNIVERSEatt(导致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

如果你想这样称呼它,一个“优点”就是你不必告诉它echo解释某些转义序列,例如\n.它知道解释它们并且不需要这样做-e

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

(注意:最后一个\n是必要的,echo暗示它,除非你给出-n选项)

相对

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

\n请注意中的最后一个printf。归根结底,这是一个品味问题要求你用什么:echo或者printf

答案4

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

相关内容