Text-Processing

為什麼 printf 比 echo 好?

  • January 10, 2022

聽說printfecho. 我只能回憶起我必須使用的一個實例,printf因為echo它不能將一些文本輸入到 RHEL 5.8 上的某個程序中,但可以printf。但顯然,還有其他差異,我想詢問它們是什​​麼以及是否有特定情況何時使用一種與另一種。

基本上,這是一個可移植性(和可靠性)問題。

最初,echo不接受任何選項,也沒有擴展任何內容。它所做的只是輸出由空格字元分隔並由換行符終止的參數。

echo "\n\t"現在,有人認為如果我們可以輸出換行符或製表符,或者可以選擇不輸出尾隨換行符,那就太好了。

然後他們更加努力地思考,但沒有將該功能添加到 shell(如perl雙引號內的位置,\t實際上意味著一個製表符),而是將其添加到echo.

David Korn 意識到了這個錯誤並引入了一種新形式的 shell 引號:$'...'後來被複製bashzsh但到那時已經太晚了。

現在,當標準 UNIXecho接收到包含兩個字元的參數\t,它不會輸出它們,而是輸出製表符。一旦\c在參數中看到,它就會停止輸出(因此也不輸出尾隨的換行符)。

其他 shell/Unix 供應商/版本選擇了不同的做法:他們添加了-e擴展轉義序列的-n選項,以及不輸出尾隨換行符的選項。有些具有-E禁用轉義序列,有些具有-n但不是-e,一個實現支持的轉義序列列表echo不一定與另一個實現支持的相同。

Sven Mascheck 有一個很好的頁面,可以顯示問題的嚴重程度

在那些echo支持選項的實現上,通常不支持 a--來標記選項的結束(echo一些非 Bourne-like shell 的內置函式支持-,但 zsh 支持),例如,很難"-n"echoin輸出許多貝殼。

bash¹ 或ksh93² 或yash($ECHO_STYLE變數 ) 等一些 shell 上,行為甚至取決於 shell 的編譯方式或環境(如果在環境中並且使用版本4, GNUecho的行為也會改變,使用它的選項,一些基於 pdksh 的選項,或者它們是否被稱為)。因此,即使來自相同版本的兩個 s,也不能保證其行為相同。$POSIXLY_CORRECT``zsh``bsd_echo``posix``sh``bash echo``bash

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實現(如bash’s )一起正常工作)。xpg_echo

file=$(echo "$var" | tr ' ' _)在大多數實現中都不好(例外情況是yashECHO_STYLE=raw需要注意的是yash’s 變數不能保存任意字節序列,因此不能保存任意文件名)和zsh’s echo -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' "$*"
)

子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. 如何改變行為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. 儘管. _ /bin/bash實際上,這不是真的,/bin/bashOracle 隨 Solaris 11(在一個可選包中)提供的似乎是用它建構的--enable-xpg-echo-default(在 Solaris 10 中不是這種情況)。

2. 如何改變行為ksh93echo

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
  1. 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``echoecho-e``-n``\c

在 2006 年發布的 ksh93r 版本的 BSD宇宙-e中添加了ksh93‘s的處理,並且可以在編譯時禁用。echo

  1. GNU echo 8.31 中的行為變化

從 coreutils 8.31(和這個 commit)開始,當 POSIXLY_CORRECT 在環境中時,GNUecho現在預設擴展轉義序列,以匹配bash -o posix -O xpg_echo’s echobuiltin 的行為(參見錯誤報告)。

5.macOSecho

大多數 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’s ECHO_STYLE=raw echo "$var"( printf '%s\n' "$var") 可以寫成:

/bin/echo "$var
\c"

zshecho -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

引用自:https://unix.stackexchange.com/questions/65803