printf

printf

echocoreutilsどこにでもあるようですが、すべてのシステムで同じ場所 (通常は ) にあるわけではありません。どこにあるか分からない場合、/bin/echoこれを呼び出す最も安全な方法は何ですか?echo

coreutilsechoバイナリがシステムに存在しない場合にコマンドが失敗しても問題ありません。これは、希望するものと異なるものを echo するよりはましです。

注: ここでの目的はechoバイナリを見つけることです。ないすべてのシェルの引数セットを見つけるにはecho 組み込みは一貫しています。たとえば、zshまたは のどちらにいるかがわからずに、 echo 組み込み関数を使用してハイフンだけを安全に印刷する方法はないようです。bash

答え1

coreutilsGNUプロジェクトによって開発されたソフトウェアバンドルであり、GNUシステムにUnixの基本ユーティリティのセットを提供します。コアユーティリティechoDebianGNU システム ( 、、、、... )ではすぐに使用できます。他のシステムでは、異なる (通常は動作が異なり、移植性が最も低いアプリケーションの 1 つである) 実装が見つかります。FreeBSD には FreeBSDtrisquelが、ほとんどの Linux ベースのシステムには busybox が、AIX には AIX が付属します...CygwinFedoraCentOSechoechoechoecho

システムによっては、複数の が存在する場合もあります (Solaris の や/bin/echoなど/usr/ucb/echo(後者は、 を取得する GNU ユーティリティ パッケージの など、Solaris の新しいバージョンではオプションになった パッケージの一部です/usr/gnu/bin/echo)。これらはすべて異なる CLI を備えています)。

GNU はほとんどの Unix 系 (さらには MS Windows などの非 Unix 系) システムに移植されているため、ほとんどのシステムで'coreutilsをコンパイルできますが、おそらくそれはあなたが探しているものではありません。coreutilsecho

また、 のバージョン間には非互換性があること(たとえば、を含むシーケンスをcoreutils echo認識しなかった) や、 の動作が環境 (変数) によって影響を受ける可能性があることにも注意してください。\x41-ePOSIXLY_CORRECT

さて、他のすべての組み込み関数と同様に、echoファイルシステム ( の検索によって見つかった$PATH) から を実行するには、 を使用するのが一般的な方法ですenv

env echo this is not the builtin echo

zsh(他のシェルをエミュレートしていない場合)では、次の操作も実行できます。

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'

正直言ってこれは悪いアイデアだと思うが、これは適切な環境で coreutils を見つけるのにかなりしっかりした仕事をするだろうecho。これらは最初から最後まで POSIX 互換のコマンドである (getconffindshgrepstringsprintfhead) なので、どこでも同じように動作するはずです。 は、getconfデフォルトのバージョンが非標準の場合に、パスの最初に各ツールの POSIX 準拠バージョンを提供します。

echoGNU の出力に表示され、プログラム テキスト内に文字通り存在する、印刷可能な文字列「GNU coreutils」と「Echo the STRING(s) to standard output」の両方を含む実行可能ファイルを検索します--help。複数のコピーがある場合は、最初に見つかったコピーが任意に選択されます。何も見つからない場合は失敗し、$(...)空の文字列に展開されます。


ただし、この (実行可能な) スクリプトがシステム上のどこかに存在すると、何らかの問題が発生する可能性があるため、これを「安全」と呼ぶことはできません。

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

繰り返しになりますが、これは非常に悪いアイデアだと思います。既知のハッシュをホワイトリストに登録しない限りecho、特定のバージョンを見つける合理的でポータブルな方法はありません。安全未知のシステムで実行します。ある時点で、推測に基づいて何かを実行する必要があります。


私はあなたにprintf代わりにコマンドを使用してくださいは、フォーマットと、文字通りに使用したい引数を受け入れます。

# printf '%s' -e
-e

printfPOSIX であり、フォーマットを指定すればすべてのシステムで同じように動作するはずです。

答え3

echo個人的には、シェル スクリプトでは完全に避け、printf '%s\n' blablabla文字列が短い場合は を使用し、文字列が長い場合は here-document を使用します。

引用元§11.14 シェル組み込み関数の制限autoconf マニュアル:

エコー

単純なことechoは、おそらく移植性の問題の最も意外な原因です。オプションとエスケープ シーケンスの両方を省略しない限り、移植性を維持することはできませんecho。オプションは期待しないでください。

引数にバックスラッシュを使用しないでください。その扱いについては合意が得られていませecho '\n' | wc -lshソラリス出力します2が、バッシュそしてズッシュshエミュレーションモードで)出力1。問題はecho、すべてのシェルが'\n'バックスラッシュと.で構成される文字列として理解することですn。コマンド置換内では、echo 'string\c'の内部状態が混乱します。キシュ88の上6.1 の場合最初の文字のみを出力しs、その後に改行を出力し、コマンド置換で次の echo の出力を完全に削除します。

これらの問題のため、任意の文字を含む文字列を に渡さないでくださいecho。たとえば、echo "$foo"は、次のことがわかっている場合にのみ安全です。フーの値にはバックスラッシュを含めることはできず、 で始まることもできません-

これが当てはまらない場合は、一般的にやprintfよりも の方が安全で使いやすいです。したがって、移植性がそれほど重要でないスクリプトでは、失敗する可能性がある場合は を使用し、同様に の代わりに を使用する必要があります。移植可能なシェル スクリプトの場合は、代わりに次のようなヒアドキュメントを使用することをお勧めします。echoecho -nprintf '%s\n'echoprintf %secho -n

          cat <<EOF
          $foo
          EOF

答え4

正直なところ、外部バイナリを明示的に呼び出す以外の方法 (特に外部バイナリの特定の実装を探す) で解決できない問題はないと思います。

なので、私は通常、「あなたがしたいことをする必要はないはずです」という結論に落ち着く回答を嫌うのですが、ここでは例外を設けています。代わりに、私が強く推奨する順に、さまざまな代替案を提案します。正しいechoバイナリを絶対に見つけなければならない場合は、Michael Homer の回答が最も適切です。また、Stéphane Chazelas の回答も読む必要があります。この回答では、バイナリが見つかるとは思わないファイルシステム内の場所がいくつか示されているからですecho。また、この回答の最後のセクションでは、「正しい」エコーの検索に関する追加の注意事項もいくつか示しています。

printf

実際にカスタム シェル スクリプトを実行することを目的としており、過去 20 ~ 30 年間で実際に使用されたシステムで、 が付属していないものを私は見たことがありません。また、GNU のような大規模な機能を搭載することに近い機能を備えたシステムで、 が付属していないものをprintf私は見たことがありません。coreutilsprintf

視点を変えれば、私はシェルスクリプトの移植性に異常なほど執着しており、文字通り現在、Bourneのようなシェルを持つシステムには、printf仮想化されたUnix v7(そう、40年ほど前のものです)、およびAndroidデバイス(所有している5台のうちの1台)があり、基本的に何もないインストールされていて、非常にロックダウンされているため、いずれにしてもすぐには役に立つシェル スクリプトは実行されません。

これにより文字列が印刷されますその通り、私は約束します、現代の誰もが使用する価値のあるあらゆるシステムについて:

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'

印刷する必要がない限りヌルバイト。そうする必要はないと思います。そうすると、テキスト全体を1つprintf への引数ともかくほとんどのシェル (zshここでは賞賛に値します) は文字列の終端文字としてヌル バイトを使用するためです。したがって、\000フォーマット文字列 (最初の引数) 内で 8 進エスケープを使用し、それを 0 個以上の%s引数と 0 個以上のその他の引数と組み合わせて、他のすべてのテキストを出力します。私の知る限り、16 進エスケープ (8 進エスケープと比較) やその他のトリックは移植性が低くなります。

提案: 入れないでください何でもあなたはしない必要特別に解析/フォーマット文字列に変換されます。printf実装によって、サポートされるフォーマットが若干異なります (最新のprintf実装を含む、例:bash組み込みとbusybox printf)。

出力に改行を追加したい場合は、\nフォーマット文字列に次のコードを追加するだけで済みます。

printf '%s\n' foo

は、厳密に明確で、どこでも同じように機能する同等のものである。

echo foo

必要な書式文字列の構築が容易ではない複雑な状況に陥った場合 (書式文字列は変数を使用してプログラム的に構築することもできることに注意してください)、 に渡す引数に常に改行リテラルを含めることprintfも、引数なしの bare を使用して改行文字のみを別途出力することもできechoます。

Here-files、または: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

唯一の注意点は、最後に改行を入れるかどうかは制御できないことです。常に意思最後に改行が入ります。ほとんどの場合、これがおそらくあなたが望んでいたことであり、あるいは問題ではありません。また、多くの (すべての?) シェルは、ヒアファイルを実装するためにディスク上の一時ファイルを使用するため、非常に制限されたシステムでこれが許可されない状況に遭遇する可能性があります (printf私が持っている、ひどく機能不全の Android インスタンスには、シェルが一時ファイルを作成するのを妨げる SELinux ポリシーまたはその他の権限制限 (正確には覚えていません) もあります)。

このため、コンピュータ セキュリティの観点から、機密情報を印刷する必要がある場合、echo正確なシステム (echo外部か組み込みか? /proc/$PID は誰でも読み取り可能かユーザーが読み取り可能か? here-file はユーザーまたは世界で読み取り可能か?) と正確な脅威モデル (脅威は実行中のプロセス情報よりもディスクをフォレンジック検索する可能性が高いか?) に応じて、here-file の方が よりも悪い場合も、良い場合もあります。

expr

のあまり知られていない機能は、expr正規表現に一致する引数内から部分文字列を抽出して印刷できることです。これは基本的に、元のecho動作 (内容をそのまま印刷し、改行文字を 1 つ追加する) の移植性の高いバージョンであり、 よりもさらに移植性の高いプレーン テキストの印刷方法です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印刷する文字列/変数の先頭にそして正規表現の先頭にサブパターンの\( \)一致/選択は重要です。前者は、印刷する値がコマンドによってキーワードexprとして誤って解釈されるのを防ぎexpr、後者は、X が実際には印刷されないようにします。

awk

以下は、awk受け取ったほとんどの単一文字列引数を一意に印刷するコンパクトなワンライナーです (最新バージョンではバックスラッシュの問題がまだありますawk- コメントでこれを思い出させてくれた Stephan に感謝します)。

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

これは Unix v7 まで遡って動作します。バックスラッシュがない場合、これは非常に移植性が高く、出力する必要があるテキストには十分かもしれません。awkスクリプト内のさまざまな実装の機能テストを記述する方echoが、作業を行うよりも簡単/シンプル/クリーンであることがわかるかもしれません。これは、 間では確かに多くの偏差がありますが、正確な出力を記述することが主な目的である場合awkよりもテストするバリエーションが少ないためです。echo

当然、変数の代わりにリテラルを使用する場合は、一重引用符で囲んだ文字列テクニックを使用します。echoその後に改行が必要な場合は、引数なしで を実行します (または、コマンドによって改行が確実に印刷されるように特定の方法を厳密に検証してください。パイプの左側の no-op コマンドを引数なしで にawk置き換えることをお勧めしますが、全体的な移植性についてそのアイデアを慎重に検証していません)。:echo

echoパイプ経由sedまたは類似の

入力が特殊でないことが分かっている場合(\000文字通り印刷したい入力のようにバックスラッシュ付きの 8 進エスケープがなく、-文字を特別に解析することを避ける必要がある場合、たとえば を印刷したい場合) 、の出力を前処理するために使用できる他のものがあれば、 を引き続き-e任意の作業に使用できます。echoecho

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

限定的で明確に定義された入力の場合、次のような簡単な置き換えで済むかもしれませんsed。必要なものによっては、だんだん難しくなる可能性があります。ある時点で、次の代替案に進む方がよいでしょう。

機能テストecho

echo苦労しても、望むとおりの印刷が確実にできるとは限らないという考えは、特に必要な出力セットがわかっている場合は必ずしも真実ではありません。そして、信じてくださいecho。ファイル システムのどこかで適切なバイナリを検索するよりも、苦労は少ないでしょう。

特に、文字を確実に印刷することについて懸念を表明されました-。残念ながら、私はまだ徹底的な機能テストのシェル スクリプト スニペットを作成していませechoんが、思いついた基本的なスニペットをいくつか示します。

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 の最近のインスタンスの多くは、8 進数以外のバックスラッシュ エスケープも解析しますが、それらの機能テストを具体的に行うこともできます (echo '\x2d'または何でも)。ただし、ほとんどの場合、echo に渡して、内容に特別な置換を行わずに印刷できる引数のセットを見つけて、必要な出力をそのまま渡すだけでよいと思います。

ニーズに応じて、echo -nテストする価値があるかもしれませんが、コマンド置換常に最後の改行を削除します (ほとんどのシェルでは最後の改行だけを削除しますが、一部のシェルでは末尾の改行すべてを削除します)。そのため、出力オプションとして考えられるのは、リテラル-nと空の文字列の 2 つです。

autoconfまた、やソースを参照することもできます。m4これらのツールは、 や他の有効な手段が見つからない場合に、明確な印刷を行うために使用できる echo を探すために特別な努力をするからですprintf

文字通り他のもの

まさに正しいものを総当たりで探す必要がないものなら何でも良いと、私は心から思っています。特定のものがインストールされないか、探している場所にインストールされないか、または自動総当たり検索によって気の毒な人のシステムが極端に遅くなるecho可能性が非常に高いです。echo/

そして、非常に可能性が低いですが、バイナリが GNU であるという指紋をパスするcoreutils echoかもしれませんが、動作が異なる可能性があります。GNU が実装を変更しなくても、誰かがインストールした GNU のバージョンをラップして、echo愚かな動作と思われる動作をしないようにする可能性があります (特別な引数を黙って削除し、必要な引数を設定すること以外は、すべての引数を透過的に渡すことはシェルスクリプトでは簡単なので、echo --help正しいテキストを印刷してもecho -e '\055'間違った動作をすることは簡単にできます)。そして、バイナリ徹底的なフィンガープリントを通過することは確かです。私は以前にも動作を変更するために生の ELF バイナリを編集したことがあり、またそうするつもりです。非常に便利な機能を有効にするため (クローズドソースのメッセージング ソフトウェアで Unicode スマイリーなどの非 ASCII バイトを含むメッセージを黙って削除しないようにするため) もあれば、PS1シェルのハードコードされたデフォルトを\$\ではなく に変更するなど、本当に些細なことの場合もあります。個人的には、実際に使用しているシステムでは、ほとんどすべての本格的な作業で を無視しているため、\w \$を にする十分な理由はありませんが、デフォルトの動作について、デフォルトの変数値について私と同じくらい強く感じている人がいるかもしれません。そのため、機能テスト に戻り、その時点で上記のセクションを参照してください。echoechoechoPS1echo

また、私のシステムでは GNU coreutils がechoとしてインストールされているgechoため、 およびおそらくインストール場所の効率的な検索PATHや という名前のファイルのみのブルートフォース検索ではecho、これらのシステムは検出されないことに注意してください。

そして、perlGNU 専用にインストールされているシステムよりも、必要な機能を実行できるようなスクリプト言語がインストールされているシステムのcoreutils echo方が多いと私は確信しています。一部のスクリプト言語は普及しており、ほとんどの場合、1 つの実装または明確に定義された仕様を持っていますが、echo実装は無数にあり、「可能な限り他の実装とは少し異なることを行う」という 1 つの仕様に正確に従いますecho

関連情報