為什麼 printf 比 echo 好?
聽說
printf
比echo
. 我只能回憶起我必須使用的一個實例,printf
因為echo
它不能將一些文本輸入到 RHEL 5.8 上的某個程序中,但可以printf
。但顯然,還有其他差異,我想詢問它們是什麼以及是否有特定情況何時使用一種與另一種。
基本上,這是一個可移植性(和可靠性)問題。
最初,
echo
不接受任何選項,也沒有擴展任何內容。它所做的只是輸出由空格字元分隔並由換行符終止的參數。
echo "\n\t"
現在,有人認為如果我們可以輸出換行符或製表符,或者可以選擇不輸出尾隨換行符,那就太好了。然後他們更加努力地思考,但沒有將該功能添加到 shell(如
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-like shell 的內置函式支持-
,但 zsh 支持),例如,很難"-n"
用echo
in輸出許多貝殼。在
bash
¹ 或ksh93
² 或yash
($ECHO_STYLE
變數 ) 等一些 shell 上,行為甚至取決於 shell 的編譯方式或環境(如果在環境中並且使用版本4, 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
’s )一起正常工作)。xpg_echo
file=$(echo "$var" | tr ' ' _)
在大多數實現中都不好(例外情況是yash
(ECHO_STYLE=raw
需要注意的是yash
’s 變數不能保存任意字節序列,因此不能保存任意文件名)和zsh
’secho -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' "$*" )
子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 --
(forecho
) 和print -rn --
(forecho -n
/\c
) 列印它們的參數以空格分隔(並且後跟一個沒有 的換行符-n
)而不進行更改(也適用於zsh
)。筆記
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
. 儘管. _/bin/bash
實際上,這不是真的,/bin/bash
Oracle 隨 Solaris 11(在一個可選包中)提供的似乎是用它建構的--enable-xpg-echo-default
(在 Solaris 10 中不是這種情況)。2. 如何改變行為
ksh93
。echo
在
ksh93
中,是否echo
擴展轉義序列並辨識選項取決於$PATH
和/或$_AST_FEATURES
環境變數的內容。如果
$PATH
包含一個包含or組件/5bin
或/xpg
之前的組件,那麼它的行為是 SysV/UNIX 方式(擴展序列,不接受選項)。如果它首先找到or或者如果7 contains ,那麼它的行為是 BSD 3方式(啟用擴展,辨識)。/bin``/usr/bin``/ucb``/bsd``$_AST_FEATURES``UNIVERSE = ucb``-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
- BSD 用於 echo -e?
對 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 中複製它不久)雖然自 90 年代初開始使用 Almquist shell 以來,BSD的
echo
內置工具就一直支持它,但迄今為止,獨立實用程序並不支持它(FreeBSD仍然不支持,儘管它確實支持Unix V7(也只是在最後一個參數的末尾))。sh``-e``echo
echo
-e``-n``\c
在 2006 年發布的 ksh93r 版本的 BSD宇宙
-e
中添加了ksh93
‘s的處理,並且可以在編譯時禁用。echo
- GNU echo 8.31 中的行為變化
從 coreutils 8.31(和這個 commit)開始,當 POSIXLY_CORRECT 在環境中時,GNU
echo
現在預設擴展轉義序列,以匹配bash -o posix -O xpg_echo
’secho
builtin 的行為(參見錯誤報告)。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
如果第一個參數是 或(自 1995 年以來)如果最後一個參數以 結尾,則 FreeBSD 會抑制換行符輸出,-n
但\c
不支持 UNIX 所需的任何其他反斜杠序列,甚至不支持\\
.6.
echo
可以逐字輸出任意數據的實現嚴格來說,您還可以計算
/bin/echo
上面的 FreeBSD/macOS(不是它們的 shell 的echo
內置),其中zsh
’secho -E - "$var"
或yash
’sECHO_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 配置設置。配置是通過(未記錄的)astgetconf()
API 完成的。在內部ksh93
,內置函式(使用或呼叫getconf
啟用)是builtin getconf``command /opt/ast/bin/getconf``astgetconf()
例如,您可以將設置
builtin getconf; getconf UNIVERSE = att
更改為(導致以 SysV 方式執行)。之後,您會注意到環境變數包含.UNIVERSE``att``echo``$_AST_FEATURES``UNIVERSE = att