なぜprintfはechoよりも優れているのですか?


548

私はそれprintfがより良いと聞いたことがありますecho。RHEL 5.8の一部のプログラムにテキストを入力するprintfために動作echoしなかったため、使用しなければならなかった私の経験から1つのインスタンスのみを思い出すことができますprintf。しかし、明らかに、他にも違いがあるので、それらの違いや、一方を使用する場合と特定の場合を使用する場合について質問したいと思います。


ふざけんecho -e
neverMind9

1
@ neverMind9はでecho -e動作せずshbash(何らかの理由で)でのみ動作し、例えばshRUN
dockerは

echo -e私にとって@CiprianTomoiagă はAndroidターミナルで動作しshます。
neverMind9

回答:


757

基本的に、これは移植性(および信頼性)の問題です。

最初は、echoオプションを受け入れず、拡張もしませんでした。実行されていたのは、スペース文字で区切られた引数を出力し、改行文字で終了することだけでした。

さて、誰かがecho "\n\t"改行文字やタブ文字を出力したり、末尾の改行文字を出力しないオプションがあればいいと思いました。

その後、彼らはより難しいと考えましたが、その機能をシェルに追加する代わりに(perl二重引用符の中に\t実際にはタブ文字を意味するように)、それをに追加しましたecho

デイビット・コーンは間違いを実現し、シェルの引用符の新しい形の導入:$'...'後でコピーされたbashzsh、それはその時によってあまりにも遅かったし。

標準のUNIX echoが2つの文字\とを含む引数を受け取るとt、それらを出力する代わりに、タブ文字を出力します。そして\c、引数を見るとすぐに出力を停止します(したがって、末尾の改行も出力されません)。

他のシェル/ Unixベンダー/バージョンは異なる-e方法を選択しました。エスケープシーケンスを展開する-nオプションと、末尾の改行を出力しないオプションを追加しました。-Eエスケープシーケンスを無効にするものもあれば、-nそうでないものもありますが-e、あるecho実装でサポートされているエスケープシーケンスのリストは、別の実装でサポートされているものと必ずしも同じではありません。

Sven Mascheckには、問題の範囲を示すすてきなページがあります

echoオプションをサポートする実装で--は、オプションの終わりをマークするためのaのサポートは通常ありません(echo一部の非Bourneのようなシェルのビルトインはサポート-しますが、zshはサポートします)。たとえば、で出力"-n"することechoは困難です多くのシェル。

以下のようないくつかのシェルにbash¹またはksh93²またはyash$ECHO_STYLE変数)、行動も、シェルがコンパイルされたか、環境(GNU方法によって異なりますecho「場合の動作も変更され$POSIXLY_CORRECTた環境でとバージョンである4zshそのとs」はbsd_echo、オプションいくつかの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)$拡張正規表現に一致するか、いずれかの引数にバックスラッシュ(またはα、BIG5文字セットを使用するロケールのようにエンコーディングにバックスラッシュ文字のエンコーディングが含まれる文字)が含まれる場合、動作は不特定。

全体として、バックスラッシュ文字が含まれecho "$var"$varおらず、先頭がでないことを確認できない限り、何が出力されるかわかりません-。その場合、POSIX仕様では実際にprintf代わりに使用するように指示されています。

つまり、echo制御されていないデータを表示するために使用することはできません。つまり、スクリプトを記述していて、外部入力(引数としてユーザーから、またはファイルシステムからのファイル名...)を取得している場合、それを使用echoして表示することはできません。

これで結構です:

echo >&2 Invalid file.

これではありません:

echo >&2 "Invalid file: $file"

(オプションがコンパイル時または環境を介して何らかの方法で有効にされていない場合、のechoような一部の(UNIXに準拠していない)実装では問題なく動作します)。bashxpg_echo

file=$(echo "$var" | tr ' ' _)ほとんどの実装でOKではない(例外があることyashECHO_STYLE=raw(警告付きyashの変数は任意のバイトシーケンスそうではない、任意のファイル名)と保持することはできませんzshs 'のecho -E - "$var"6)。

printf、一方で、少なくともの基本的な使用法に限定されている場合は、より信頼性が高くなりechoます。

printf '%s\n' "$var"

$var含まれる文字に関係なく、改行文字が続くコンテンツを出力します。

printf '%s' "$var"

末尾の改行文字なしで出力します。

現在、printf実装にも違いがあります。POSIXで指定されている機能の中核がありますが、多くの拡張機能があります。たとえば%q、引数を引用するa をサポートするものもありますが、その実行方法はシェルごとに異なり\uxxxx、ユニコード文字をサポートするものもあります。printf '%10s\n' "$var"マルチバイトロケールでは動作が異なります。少なくとも3つの異なる結果があります。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' "$*"
)

サブシェル(ほとんどのシェル実装で余分なプロセスを生成することを意味します)はlocal IFS、多くのシェルで使用するか、次のように記述することで回避できます。

echo() {
  if [ "$#" -gt 0 ]; then
     printf %s "$1"
     shift
  fi
  if [ "$#" -gt 0 ]; then
     printf ' %s' "$@"
  fi
  printf '\n'
}

ノート

1. bashecho動作を変更する方法。

を使用するbashと、実行時にecho(関数またはエイリアスとして、または関数またはエイリアスとしてenable -n echo再定義する)の動作を制御する2つのことがechoありxpg_echo bashます:オプションとbashposixモードであるかどうか。が呼び出された場合、または環境内にある場合、またはオプションをposix使用してモードを有効にできます。bashshPOSIXLY_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_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は、出力-eされないため、POSIX準拠ではないことに注意してください。

$ 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. ksh93echo動作を変更する方法。

ではksh93echoエスケープシーケンスを展開してオプションを認識するかどうかは$PATH$_AST_FEATURES環境変数や環境変数の内容によって決まります。

またはコンポーネントの前または前の$PATHコンポーネントが含まれている場合、SysV / UNIXのように動作します(シーケンスを展開し、オプションを受け入れません)。最初に見つかった場合、または7が含まれている場合、BSD 3の方法で動作します(拡張を有効にするため、認識します)。/5bin/xpg/bin/usr/bin/ucb/bsd$_AST_FEATURESUNIVERSE = ucb-e-n

デフォルトはシステム依存、Debian上のBSDです(builtin getconf; getconf UNIVERSEksh93の最近のバージョンの出力を参照):

$ 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. echo -eのBSD?

-eオプションの処理に関するBSDへの参照は、ここでは少し誤解を招く可能性があります。これらの異なる互換性のないecho動作のほとんどは、AT&Tですべて導入されました。

  • \n\0ooo\cプログラマのワークベンチ(UnixのV6に基づいて)UNIX、および残りの(で\b\rUnixのシステムIIIで...)のRef
  • -nUnix V7で(デニスリッチーRefによる
  • -eUnix V8(デニスリッチー参照
  • -Eそれ自体はおそらく最初に由来したものですbashバージョン1.13.5の CWRU / CWRU.chlog では、1992-10-18にBrian Foxが追加し、echo10日後にリリースされたsh-utils-1.8 でGNUがそれをコピーすることを言及しています)

一方でechoの組み込みshやBSD系を支えてきた-e彼らは90年代初頭にそれのためにAlmquistシェルを使用して開始した日から、スタンドアロンのechoこの日にユーティリティが(そこをサポートしていません。FreeBSDはecho、まだサポートされていない-e、それはサポートしていても、-n同じようUnix V7(そして\c最後の引数の最後のみ)。

取り扱い-eに追加されたksh93echoBSDの中で時に宇宙と2006年にリリースされksh93rバージョンでコンパイル時に無効にすることができます。

4. 8.31でのGNUエコーの動作変更

coreutils 8.31(およびこのcommit)以降、GNU echoはPOSIXLY_CORRECTが環境にある場合、デフォルトでエスケープシーケンスを展開し、bash -o posix -O xpg_echoecho組み込みの動作に一致するようになりました(バグレポートを参照)。

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は、最初の引数が-n(1995年以降)最後の引数がで終わる場合に改行出力を抑制する\cが、UNIXで必要な他のバックスラッシュシーケンスをサポートしないFreeBSDのものです\\

6. echo任意のデータを逐語的に出力できる実装

厳密には、あなたもFreeBSDの/ MacOSのことを数えることができる話す/bin/echo(ない自分の殻の上のecho組み込みが)どこzshecho -E - "$var"yashのはECHO_STYLE=raw echo "$var"printf '%s\n' "$var")に記述できます。

/bin/echo "$var
\c"

サポートする実装-Eおよび-n(またはに設定することができますが)も行うことができます。

echo -nE "$var
"

そしてzshecho -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 = attUNIVERSE設定をattecho特にSysVの動作をさせる)に変更します。そうすると、$_AST_FEATURES環境変数にが含まれていることに気付くでしょうUNIVERSE = att


13
初期のUNIX開発の多くは単独で行われ、「インターフェイスを変更すると名前を変更する」などの優れたソフトウェアエンジニアリングの原則 は適用されませんでした。
ヘンクランゲベルド

7
注釈として、引用構文の一部としてシェルとは対照的にシーケンスをecho展開することの1つの(そしておそらく唯一の)利点\xは、その後NULバイトを出力できることです(Unixの別の誤設計は、おそらくヌルで区切られたものです(のような半分のシステムコールは、文字列、execve()))バイトの任意のシーケンスを取ることができない
ステファンChazelas

Sven MaschekのWebページで見られるprintfように、ほとんどの実装では間違っているため、問題であるようです。私が知っている唯一の正しい実装はでboshあり、Sven Maschekのページにはを介してnullバイトの問題がリストされていません\0
気違い

1
これはすばらしい答えです。@StéphaneChazelasに書いてくれてありがとう。
JoshuaRLi

28

printf書式設定オプションに使用する場合があります。echo変数または(単純な)行の値を出力する場合に役立ちますが、それだけです。printf基本的に、Cバージョンの機能を実行できます。

使用例と機能の例:

Echo

echo "*** Backup shell script ***"
echo
echo "Runtime: $(date) @ $(hostname)"
echo

printf

vech="bike"
printf "%s\n" "$vech"

ソース:


@ 0xC0000022L私は感謝を訂正します。質問に答えるために急いで間違ったサイトにリンクしたことに気付きませんでした。あなたの貢献と訂正をありがとう。
NlightNFotis

7
echo変数の値にメタ文字が含まれている場合、変数の印刷に使用できません。
キーストンプソン

17

「利点」の1つは、それを呼び出したい場合、echoなどの特定のエスケープシーケンスを解釈するように伝える必要がないことです\n。それらを解釈することは知っている-eので、そうする必要はありません。

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

(注意:オプションを指定しない限り\n、最後が必要echoです。-n

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

の最後\nに注意してくださいprintf。結局のところ、使用するものは好みと要件の問題です:echoまたはprintf


1
以下のための真/usr/bin/echoおよびbash組み込み。dashkshおよびzsh組み込みはecho必要ありません-eバックスラッシュでエスケープ文字を拡大してスイッチを。
マナトワーク

なぜ恐怖が引用するのですか?あなたの言葉遣いは、それが必ずしも本当の利点ではないことを暗示しています。
キーストンプソン

2
@KeithThompson:実際に彼らが示唆ているのは、誰もがそれを利点と見なすわけではないということです。
0xC0000022L

それを拡大していただけますか?なぜそれが利点にならないのでしょうか?「それを呼び出したい場合」というフレーズは、そうではないと思うことをかなり強く暗示しています。
キーストンプソン

2
または、あなたはただすることができましたprintf '%s\n' 'foo\bar
nyuszika7h

6

欠点の1つprintfは、組み込みシェルechoがはるかに高速であるためパフォーマンスです。これは特に、新しいコマンドの各インスタンスが重いWindowsオーバーヘッドを引き起こすCygwinで効果があります。エコーが多いプログラムを使用/bin/echoからシェルのエコーに変更すると、パフォーマンスはほぼ2倍になりました。移植性とパフォーマンスのトレードオフです。常に使用するのはスラムダンクではありませんprintf


15
printf最近のほとんどのシェルに組み込まれています(bash、dash、ksh、zsh、yash、いくつかのpdksh派生... cygwinで一般的に見られるシェルも含まれます)。唯一の注目すべき例外は、いくつかのpdksh派生物です。
ステファンシャゼル14年

しかし、それらのprintf実装の多くは壊れています。これはprintf、nulバイトを出力するために使用する場合に不可欠ですが、一部の実装では、フォーマット文字列内の `\ c 'を解釈するべきではありません。
気味悪い

2
@schily、フォーマット文字列でprintf使用する場合の動作はPOSIXで指定されていない\cため、printf実装はその点で何でもできます。たとえば、一部はPWBと同じように処理しecho(printfを終了させます)、kshはそれを\cA制御文字(printf形式の引数およびfor $'...'ではなく、!ではechoなくprint)に使用します。それはNULバイトの印刷に関係している、または多分あなたが参照のうえしているのかわかりませんkshprintf '\c@'
ステファンシャゼル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.