基本上,这是可移植性(和可靠性)问题。
最初,echo
不接受任何选择,也没有扩展任何东西。它所做的只是输出其参数,该参数由空格分隔并由换行符终止。
现在,有人认为,如果我们可以做一些事情echo "\n\t"
来输出换行符或制表符,或者可以选择不输出尾随的换行符,那将是很好的选择。
然后,他们更努力地思考,但没有将功能添加到外壳中(例如perl
在双引号中\t
实际上表示制表符),而是将其添加到中echo
。
大卫·科恩(David Korn)意识到了这个错误,并引入了一种新的shell引号形式:$'...'
后来被抄袭了bash
,zsh
但是到那时还为时已晚。
现在,当标准UNIX echo
接收到包含两个字符\
和的参数时t
,而不是输出它们,而是输出一个制表符。并且一旦\c
在参数中看到它,它就会停止输出(因此也不输出结尾的换行符)。
其他shell / Unix供应商/版本选择了不同的方法:他们添加了-e
扩展转义序列的-n
选项,以及不输出尾随换行符的选项。有些具有-E
禁用转义序列的功能,有些具有-n
但不具备-e
,一种echo
实现所支持的逸出序列的列表不一定与另一种实现所支持的相同。
Sven Mascheck的页面漂亮,可以显示问题的严重程度。
在那些echo
支持选项的实现中,通常不支持a --
来标记选项的结束(echo
某些非Bourne类shell 的内置功能,而zsh支持-
该功能),例如,很难"-n"
用echo
in 输出许多贝壳。
在诸如bash
¹或ksh93
²或yash
($ECHO_STYLE
变量)之类的某些shell上,其行为甚至取决于shell的编译方式或环境(echo
如果$POSIXLY_CORRECT
在环境中,并且版本为4(zsh
带有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
实现(虽然可以正常使用),但是)。bash
xpg_echo
file=$(echo "$var" | tr ' ' _)
是不是在大多数实现OK(例外是yash
与ECHO_STYLE=raw
(与该警告yash
的变量不能持有字节的任意序列,以便不任意文件名)和zsh
的echo -E - "$var"
6)。
printf
另一方面,至少在仅限于的基本用法时更可靠echo
。
printf '%s\n' "$var"
将输出$var
尾随换行符的内容,无论其可能包含什么字符。
printf '%s' "$var"
将输出它而没有尾随换行符。
现在,printf
实现之间也存在差异。POSIX指定了功能的核心,但随后有很多扩展。例如,某些支持a %q
引用引数,但如何完成它因外壳而异,某些支持\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' "$*"
)
可以避免将subshell(在大多数shell实现中产生额外的进程)local IFS
与许多shell一起使用,或者通过像下面这样编写来避免:
echo() {
if [ "$#" -gt 0 ]; then
printf %s "$1"
shift
fi
if [ "$#" -gt 0 ]; then
printf ' %s' "$@"
fi
printf '\n'
}
笔记
1.怎样bash
的echo
行为可以被改变。
使用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/bash
Oracle随Solaris 11(在一个可选软件包中)一起提供的似乎是用它构建的--enable-xpg-echo-default
(Solaris 10中不是这种情况)。
2.怎样ksh93
的echo
行为可以被改变。
在中ksh93
,是否echo
扩展转义序列以及是否识别选项取决于$PATH
and和/或$_AST_FEATURES
环境变量的内容。
如果$PATH
包含的组件包含/5bin
或/xpg
之前/bin
或/usr/bin
之后,则它将以SysV / UNIX方式运行(扩展序列,不接受选项)。如果找到,/ucb
或者/bsd
首先找到,或者如果$_AST_FEATURES
7包含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
加入ksh93
的echo
时候在BSD 宇宙在2006年发布和ksh93r版本可以在编译时被禁用。
4. GNU echo在8.31中的行为变化
由于coreutils的8.31(和这次提交),GNU echo
现在扩大逃脱默认序列时POSIXLY_CORRECT的环境,以配合的行为bash -o posix -O xpg_echo
的echo
内置(见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
内置文件)可以在其中写入zsh
s echo -E - "$var"
或yash
s ECHO_STYLE=raw echo "$var"
(printf '%s\n' "$var"
):
/bin/echo "$var
\c"
支持-E
和-n
(或可以配置为)的实现还可以执行以下操作:
echo -nE "$var
"
和zsh
的echo -nE - "$var"
(printf %s "$var"
)可以写成
/bin/echo "$var\c"
7. _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
环境变量contains UNIVERSE = att
。
echo -e
?