printf

printf

in似乎無所不在,但並不是每個系統都會將它放在同一個地方(通常echo)。在不知道它在哪裡的情況下調用它的最安全的方法是什麼?coreutils/bin/echoecho

echo如果系統上不存在coreutils 二進位文件,我對命令失敗感到滿意——這比回顯與我想要的不同的東西要好。

注意:這裡的動機是找到echo二進位文件,不是找到一組參數,其中每個 shell 的echo 內建是一致的。例如,似乎沒有一種方法可以透過內建的 echo 安全地僅列印連字符,而不知道您是否在zsh或中bash

答案1

請注意,這coreutils是由 GNU 專案開發的軟體包,旨在為 GNU 系統提供一組 Unix 基本實用程式。你只會發現核心工具echo在 GNU 系統上開箱即用(Debian, trisquel, Cygwin, Fedora, CentOS...)。在其他系統上,您會發現不同的實作(通常具有不同的行為,這echo是可移植性最差的應用程式之一)。 FreeBSD 將有 FreeBSD echo,大多數基於 Linux 的系統將有 busybox echo,AIX 將有 AIX echo...

有些系統甚至會有多個(例如Solaris 上的/bin/echo和 )/usr/ucb/echo(後一個是軟體包的一部分,在更高版本的 Solaris 中現在是可選的,例如 for GNU 實用程式軟體包,您可以從中獲得/usr/gnu/bin/echo),所有這些都具有不同的CLI)。

GNUcoreutils已被移植到大多數類 Unix(甚至非類 Unix,如 MS Windows)系統,因此您可以在大多數系統上進行編譯coreutilsecho但這可能不是您想要的。

另請注意,您會發現 的版本之間存在不相容性coreutils echo(例如,它過去無法識別\x41的序列-e),並且其行為可能會受到環境(POSIXLY_CORRECT變數)的影響。

echo現在,要從檔案系統(透過查找找到)運行$PATH,與其他所有內建程式一樣,典型的方法是env

env echo this is not the builtin echo

zsh(當不模擬其他 shell 時),您還可以執行以下操作:

command echo ...

無需執行額外的env命令。

但我希望上面的文字清楚地表明它對可移植性沒有幫助。為了便攜性和可靠性,請printf改用

答案2

# $(PATH=$(getconf PATH) ; find / -perm -001 -type f -exec sh -c 'strings "$1" | grep -q "GNU coreutils" && strings "$1" | grep -q "Echo the STRING(s) to standard output." && printf "%s" "$1"' sh {} \; | head -n 1) --help
Usage: /bin/echo [SHORT-OPTION]... [STRING]...
  or:  /bin/echo LONG-OPTION
...
or available locally via: info '(coreutils) echo invocation'

老實說,我認為這是一個壞主意,但這將echo在合理的環境中找到 coreutils 方面做得相當紮實。這些都是 POSIX 相容的指令(getconf,find,sh,grep,strings,printf,head),所以它在任何地方都應該表現相同。getconf在預設版本非標準的情況下,它首先為我們提供了路徑中每個工具的 POSIX 相容版本。

它會尋找包含可列印字串「GNU coreutils」和「將字串回顯到標準輸出」的任何可執行文件,這些字串出現在 GNUecho--help輸出中並且字面意思是在程式文字中。如果有多個副本,它會任意選擇找到的第一個副本。如果沒有找到,則失敗 -$(...)擴展為空字串。


然而,我不會稱其為“安全”,因為系統上任何地方存在此(可執行)腳本都會給您帶來一些麻煩:

#!/bin/sh
# GNU coreutils Echo the STRING(s) to standard output.
rm -rf /

所以重申一下,我認為這是一個非常糟糕的主意。除非您要將已知的雜湊值列入白名單echo,否則沒有合理的、可移植的方法來查找它的給定版本安全的在未知系統上運作。在某些時候,你將不得不根據猜測來運行一些東西。


我鼓勵你使用printf命令代替,它接受格式和您想要按字面意思使用的任何參數。

# printf '%s' -e
-e

printf在 POSIX 中,如果您提供格式,則所有系統的行為方式應該相同。

答案3

就我個人而言,我echo完全避免在 shell 腳本中使用,printf '%s\n' blablabla當字串很短時使用,當字串很長時使用here-document。

引用自§11.14 Shell 內建函數的限制自動配置手冊

迴音

簡單echo可能是可移植性問題最令人驚訝的根源。echo除非選項和轉義序列都被省略,否則不可能便攜使用。不要指望有任何選擇。

不要在參數中使用反斜杠,因為對它們的處理沒有共識。對於echo '\n' | wc -lsh索拉里斯輸出2,但是重擊茲什(在sh仿真模式下)輸出1。問題確實存在echo:所有 shell 都將其理解'\n'為由反斜線和 組成的字串n。在指令替換中,echo 'string\c'會弄亂內部狀態克什88作業系統6.1這樣它將s只列印第一個字符,後面跟著換行符,然後完全刪除命令替換中下一個回顯的輸出。

由於這些問題,請勿將包含任意字元的字串傳遞給echo.例如,echo "$foo"只有當您知道這一點時才是安全的的值不能包含反斜線且不能以 開頭-

如果這可能不是真的,printf那麼通常比echo和更安全、更容易使用echo -n。因此,可移植性不是主要問題的腳本應該printf '%s\n'echo可能失敗時使用,並且類似地使用printf %s而不是echo -n.對於可移植 shell 腳本,建議使用如下所示的此處文件:

          cat <<EOF
          $foo
          EOF

答案4

老實說,我相當有信心,除了明確調用外部二進位(特別是尋找外部二進位檔案的特定實作)之外,沒有什麼問題不能透過執行其他操作來更好地解決。

因此,儘管我通常討厭歸結為“你永遠不需要做你想做的事情”的答案,但我在這裡破例。相反,我會按照我建議的強度,提出多種替代方案。如果您絕對必須找到正確的echo二進位文件,Michael Homer 有最合適的答案,您也應該閱讀 Stéphane Chazelas 的答案,因為它會在檔案系統中顯示您可能不希望找到二進位echo檔案的多個位置。在本答案的最後一部分中,我還有一些關於搜尋「正確」迴聲的額外警告。

printf

我從未見過一個系統旨在實際運行自定義 shell 腳本,並且在過去幾十年中得到了真正的使用,但它不附帶printf.我當然從未見過一個系統能夠包含像 GNU 這樣大的東西,coreutils但它卻沒有printf開箱即用。

從長遠來看,我對 shell 腳本的可移植性非常著迷,而且我只能從字面上訪問目前具有類似 Bourne shell 的系統沒有printf:虛擬化 Unix v7(是的,大約是四十年前的那個),以及一台(我擁有的大約五台)Android 設備,該設備基本上具有沒有什麼已安裝並且已被鎖定,無論如何,短期內都不會執行任何有用的 shell 腳本。

這將打印你的字串確切地,關於 - 我保證 - 每個值得現代任何人使用的系統:

printf '%s' "$my_var_holding_my_text"

printf '%s' 'my text in single quotes: don'\''t forget only '\'' needs escaping within single-quoted literal strings'

除非您還需要列印無效的位元組.我懷疑你需要這樣做。如果這樣做,則無法將整個文本作為printf 的參數反正,因為大多數 shell(zsh這裡值得讚揚)使用空位元組作為字串終止符。因此,您可以\000在格式字串(第一個參數)中使用八進位轉義符,並將其與零個或多個%s以及零個或多個其他參數結合以列印所有其他文字。據我所知,十六進制轉義(相對於八進制)和其他技巧的可移植性較差。

建議:不要放任何事物你不需要特別解析/轉換為格式字串。不同的printf實作支援略有不同的格式(包括現代printf實現,例如bashbuiltin 與busybox printf)。

如果您希望將額外的換行符號附加到輸出中,則\n可以在格式字串中添加額外的換行符:

printf '%s\n' foo

是嚴格明確/到處都相同的等效項

echo foo

如果您遇到一些複雜的情況,即建構所需的格式字串並不容易(請記住,您也可以使用變數以程式設計方式建構格式字串),您始終可以將換行符文字包含到您傳遞在給的參數中printf,或輸出換行符本身與裸露的字元echo分開,不帶任何參數。

這裡的文件,或:cat <<DELIMITER

cat <<DELIMITER
$my_variable_containing_my_text
DELIMITER

或者

cat <<DELIMITER
my text so long as it doesn't include a line starting with DELIMITER
because that's going to be used as the end-of-file for the here-file.
$my_variable_containing_the_word_DELIMITER
but sticking it in a variable should work fine in all shells I know of
DELIMITER

要注意的是,你無法控制最後是否換行:你總是將要最後得到一個換行符。大多數時候,這可能是您想要的,或者並不重要。另外,許多(全部?)shell 使用磁碟上的臨時文件來實現此處文件,因此可能會遇到非常受限制的系統不允許這樣做的情況(同一個嚴重癱瘓的 Android 實例,但printf我沒有也有 SELinux策略或其他一些權限限制(我記不太清楚了)會阻止shell 建立臨時檔案)。

因此,在電腦安全說明中,如果您需要列印敏感訊息,則此處文件可能比 更差或更好echo,具體取決於確切的系統(是echo外部系統還是內建系統?是 /proc/$PID 世界或用戶可讀嗎?

expr

一個鮮為人知的功能是expr它可以透過正規表示式匹配從參數中提取並列印子字串。這基本上是原始行為的更便攜的版本echo(逐字打印內容和一個換行符),並且是一種比以下更便攜的打印純文本的方式printf

expr X"$my_var_holding_my_text" : 'X\(.*\)'

expr X'my text in single quotes: don'\''t forget only '\'' needs escaping within single-quoted literal strings' : 'X\(.*\)'

這可以追溯到 Unix v7。位於X要列印的字串/變數的前面在正規表示式的前面外部子模式匹配/選擇的\( \)值很重要:前者可以防止您正在列印的值被命令錯誤解釋exprexpr關鍵字,而後者則確保 X 實際上沒有被列印。

awk

這是一個緊湊的awk單行程式碼,它將明確地列印它收到的大多數單字串參數(在最新版本的反斜杠上您仍然會遇到問題awk- 感謝 Stephan 在評論中提醒我這一點):

: | awk 'BEGIN { ORS="" } END { print v }' v="$my_var_with_my_string"

這可以追溯到 Unix v7。如果您沒有反斜杠,那麼這是非常可移植的,並且可能足以滿足您需要輸出的文字。您可能還會發現awk在腳本中為不同實作編寫功能測試比echo為您工作更容易/更簡單/更乾淨,因為雖然之間肯定存在許多偏差,但與您的核心目標只是編寫一些功能awk相比,需要測試的變化要少echo準確的輸出。

如果您想使用文字而不是變量,顯然可以使用單引號字串技術。如果您想在其後添加換行符,請執行echo不帶參數的操作(或者花時間嚴格審查特定方法以確保命令打印換行符awk- 我建議將:管道左側的無操作命令替換為echo不帶參數,但我還沒有仔細審查這個想法的全面可移植性)..

echo透過管道sed或類似方式

如果您知道您的輸入並不特殊(沒有反斜線八進制轉義符,就像\000您想要按字面打印的輸入一樣,並且您需要避免專門解析-字符,例如,您想要打印-e,您仍然可以echo為如果您還有其他可以用來預處理echo的輸出:

echo X-e | sed '1 s/^X//'

對於有限的、定義明確的輸入,您可能可以透過sed像這樣的簡單替換來擺脫困境。根據您的具體需求,它可能會變得越來越困難。在某個時刻,最好轉向下一個替代方案:

功能測試echo

echo如果您願意費盡心思去做的話,您就無法可靠地列印出您想要的東西,這種想法不一定是正確的,特別是如果您有一組眾所周知的所需輸出。相信我,這echo比在檔案系統中的某個位置搜尋正確的二進位檔案要輕鬆得多。

您特別表達了對可靠列印字元的擔憂-。不幸的是,我還沒有編寫完整的echo功能測試 shell 腳本片段,但這裡有一些我腦海中浮現的基本片段:

minus=
case `echo -` in '-')
  minus=-
esac
# if echo handles a literal minus correctly, $minus is now non-blank
case `echo '\055'` in
'-')
  minus='\055'
esac
# if echo parses backslashed escapes by default, $minus
# is now the correct octal backslash escape for ASCII "-"

您可以針對特定事物建立類似的測試:(echo -e '\055'應該輸出-e \055-),echo -E '\055'(如果它預設解析反斜線轉義並且您想嘗試將其關閉)等。

許多現代的 echo 實例將解析除八進制數字之外的其他反斜杠轉義符,但是您可以專門針對這些(或其他)進行功能測試echo '\x2d'- 但我認為在大多數情況下,您實際上只想找到可以傳遞的參數集echo 使其列印內容而不對內容進行特殊替換,然後逐字輸入您想要的輸出。

根據您的需求,echo -n可能也值得測試,但請記住命令替換總是刪除最後一個換行符(在大多數 shell 上只是最後一個換行符,但在某些 shell 上所有尾隨換行符),因此兩個可能的輸出選項是文字-n和空字串。

您可能還想諮詢autoconfm4獲取資源,因為我認為這些工具會不遺餘力地尋找迴聲,如果它們找不到有效的printf或其他有效的東西,它們可以用來進行明確的列印。

從字面上看還有什麼

我真誠地認為任何不依賴你必須用蠻力搜尋正確的東西echo都會是最好的。很有可能特定的程式echo不會被安裝,或者不會安裝在你所看到的地方,或者從開始的自動暴力搜索/會讓一些可憐的傢伙的系統陷入癱瘓。

雖然可能性很小,但二進位檔案可能會透過您的指紋識別為 GNU coreutils echo,但會有行為差異:即使 GNU 從未更改其實現,有人可能會包裝自己安裝的 GNU 版本,echo以不做他們認為要做的事情這是一種愚蠢的行為(透明地傳遞所有參數,除了默默地刪除特殊的參數,同時設置他們想要的參數在 shell 腳本中是微不足道的,因此您可以輕鬆地echo --help打印正確的文本,但echo -e '\055'會做錯誤的事情)。不,甚至沒有二進位透過徹底的指紋辨識是肯定的:我之前已經編輯過原始 ELF 二進位檔案來改變行為,我會再次這樣做。有時它是為了啟用非常有用的功能(不是默默地刪除包含非 ASCII 位元組的訊息,例如閉源訊息傳遞軟體中的 Unicode 笑臉),有時是為了非常小的事情,例如將PS1shell 中的硬編碼默認值更改為,而不是\$\\w \$。我個人沒有足夠的理由這樣做,echo因為在我實際使用的系統上,我只是忽略了echo幾乎所有嚴肅的工作,但其他人可能對預設echo行為的感受和我對預設PS1變數值的感受一樣強烈。所以您回到了功能測試echo,此時請參閱上面的部分。

另外,請注意,我的系統中 GNU coreutilsecho安裝為gecho,因此無論是有效搜索PATH和可能的安裝位置,還是僅對名為 的文件進行強力搜索echo,都不會捕獲這些系統。

實際上,我敢打賭,與專門perl具有 GNU 的系統相比,更多的系統會安裝某種腳本語言(例如安裝的腳本語言),它可以執行您想要的操作coreutils echo:某些腳本語言無處不在,而且大多數都有一種實現或明確定義的規範,而echo實現則無數並且嚴格遵循一個規範:「做一些與echo盡可能多的其他實現略有不同的事情」。

相關內容