はい、次のような多くのことがわかります。
while read line; do
echo $line | cut -c3
done
またはさらに悪いこと:
for line in `cat file`; do
foo=`echo $line | awk '{print $2}'`
echo whatever $foo
done
(笑わないでください、私はそれらの多くを見てきました)。
一般的には、シェルスクリプトの初心者から。これらは、Cやpythonのような命令型言語で行うことの素朴な文字通りの翻訳ですが、それはシェルで物事を行う方法ではありません。ほとんどのバグを修正するために、コードは判読できなくなります。
概念的に
C言語または他のほとんどの言語では、ビルディングブロックはコンピューターの指示の1レベル上にあります。プロセッサに何をすべきか、そして次に何をすべきかを指示します。プロセッサを手に取り、それを細かく管理します。そのファイルを開き、そのバイト数を読み取り、これを行い、それでそれを行います。
シェルは高レベルの言語です。それは言語でさえないと言うかもしれません。それらはすべてのコマンドラインインタープリターの前にあります。ジョブは実行するコマンドによって実行され、シェルはそれらを調整することのみを目的としています。
Unixが導入したすばらしいことの1つは、パイプと、すべてのコマンドがデフォルトで処理するデフォルトのstdin / stdout / stderrストリームでした。
45年の間に、コマンドの力を活用し、タスクに協力させるAPIほど優れたものは見つかりませんでした。それがおそらく今日でも人々がシェルを使用している主な理由です。
切断ツールと音訳ツールがあり、簡単に実行できます。
cut -c4-5 < in | tr a b > out
シェルは単に配管を行って(ファイルを開き、パイプをセットアップし、コマンドを呼び出します)、すべて準備ができたら、シェルは何もせずに流れます。これらのツールは、一方が他方をブロックしないように十分なバッファリングを使用して、効率的に独自のペースで同時に作業を行います。
ただし、ツールを呼び出すにはコストがかかります(パフォーマンスポイントで開発します)。これらのツールは、Cで何千もの命令で記述されている場合があります。プロセスを作成し、ツールをロード、初期化、クリーンアップ、プロセスを破棄し、待機する必要があります。
呼び出しcut
は、キッチンの引き出しを開け、ナイフを取り、使用し、洗浄し、乾燥させ、引き出しに戻すようなものです。あなたがするとき:
while read line; do
echo $line | cut -c3
done < file
ファイルの各行ごとにread
、キッチンの引き出しからツールを取得するように設計されています(そのために設計されていないため非常に不格好なものです)。次にecho
、cut
ツールとツールの会議をスケジュールし、引き出しから取り出し、呼び出し、洗浄し、乾燥させ、引き出しに戻します。
これらのツール(のいくつかはread
とecho
)ほとんどのシェルに組み込まれているが、それはほとんどので、ここで違いはありませんecho
し、cut
まだ別のプロセスで実行する必要があります。
タマネギを切るようなものですが、ナイフを洗って、各スライスの間にあるキッチンの引き出しに戻します。
ここで明らかな方法は、cut
ツールを引き出しから取り出し、玉ねぎ全体をスライスし、作業全体が完了した後に引き出しに戻すことです。
IOW、特にテキストを処理するシェルでは、できるだけ少ないユーティリティを呼び出してタスクに協力させます。数千のツールを順番に実行して、各ツールが開始、実行、クリーンアップされてから次のツールを実行しないようにします。
ブルースのすばらしい答えのさらなる読書。シェルの低レベルのテキスト処理内部ツール(を除くzsh
)は制限されており、扱いにくく、一般的なテキスト処理には一般的に適合しません。
性能
前述したように、1つのコマンドを実行するにはコストがかかります。そのコマンドが組み込まれていない場合、莫大な費用がかかりますが、たとえそれらが組み込まれていても、費用は大きいです。
そして、シェルはそのように動作するように設計されておらず、パフォーマンスの高いプログラミング言語であるというふりをしていません。これらはコマンドラインインタープリターではありません。したがって、この面ではほとんど最適化が行われていません。
また、シェルは別々のプロセスでコマンドを実行します。これらのビルディングブロックは、共通のメモリまたは状態を共有しません。fgets()
またはfputs()
Cで行う場合、それはstdioの関数です。stdioは、すべてのstdio関数の入出力用の内部バッファーを保持し、コストのかかるシステム呼び出しを頻繁に行わないようにします。
対応するも、組み込みシェルユーティリティ(read
、echo
、printf
)それを行うことはできません。read
1行を読むためのものです。改行文字を超えて読み取られる場合、それは、次に実行するコマンドが改行文字を逃すことを意味します。そのread
ため、入力を一度に1バイトずつ読み取る必要があります(入力がチャンクを読み取ってシークバックするという点で通常のファイルである場合、実装によっては最適化が行われますが、通常のファイルでのみ機能しbash
、たとえば128バイトのチャンクのみを読み取りますまだテキストユーティリティが行うよりもはるかに少ないです)。
出力側でecho
も同じですが、単に出力をバッファリングすることはできません。次に実行するコマンドはそのバッファを共有しないため、すぐに出力する必要があります。
明らかに、コマンドを順番に実行することはコマンドを待つ必要があることを意味します。シェルからツールへ、そしてツールへ制御を戻す小さなスケジューラーダンスです。また、(パイプラインで長時間実行されるツールのインスタンスを使用するのではなく)利用可能な場合、複数のプロセッサを同時に利用できないことも意味します。
そのwhile read
ループと(おそらく)同等のcut -c3 < file
テストとの間に、私のクイックテストでは、テストのCPU時間の比率が約40000(1秒対半日)です。ただし、シェル組み込みコマンドのみを使用する場合でも:
while read line; do
echo ${line:2:1}
done
(ここではbash
)、それはまだ約1:600です(1秒対10分)。
信頼性/読みやすさ
そのコードを正しくするのは非常に難しいです。私が与えた例は、実際にはあまりにも頻繁に見られますが、多くのバグがあります。
read
は、さまざまなことを実行できる便利なツールです。ユーザーからの入力を読み取り、単語に分割してさまざまな変数に保存できます。 read line
んではない入力のラインを読み、または多分それは非常に特別な方法で行を読み取ります。実際には、入力から単語を読み取ります。単語は$IFS
、区切り文字または改行文字をエスケープするためにバックスラッシュを使用できます。
次の$IFS
ような入力では、デフォルト値がの場合:
foo\/bar \
baz
biz
read line
に保存"foo/bar baz"
されますが、予想どおり$line
ではありません" foo\/bar \"
。
行を読むには、実際に次のものが必要です。
IFS= read -r line
それはあまり直感的ではありませんが、それはそうです、シェルはそのように使用されることを意図していなかったことを思い出してください。
同じですecho
。echo
シーケンスを展開します。ランダムファイルのコンテンツのような任意のコンテンツには使用できません。printf
代わりにここが必要です。
そしてもちろん、誰もが陥る変数を引用するという典型的な忘却があります。だからそれはもっとです:
while IFS= read -r line; do
printf '%s\n' "$line" | cut -c3
done < file
ここで、さらにいくつかの注意事項があります。
- ただし
zsh
、入力にNUL文字が含まれている場合は機能しませんが、少なくともGNUテキストユーティリティには問題はありません。
- 最後の改行の後にデータがある場合、それはスキップされます
- ループ内では、stdinがリダイレクトされるため、その中のコマンドがstdinから読み取られないことに注意する必要があります。
- ループ内のコマンドについては、それらが成功するかどうかに注意を払っていません。通常、エラー(ディスクがいっぱい、読み取りエラーなど)の状態は適切に処理されず、通常は適切な同等の状態よりも処理が悪くなります。
上記の問題のいくつかに対処したい場合は、次のようになります。
while IFS= read -r line <&3; do
{
printf '%s\n' "$line" | cut -c3 || exit
} 3<&-
done 3< file
if [ -n "$line" ]; then
printf '%s' "$line" | cut -c3 || exit
fi
それはますます読みにくくなっています。
引数を介してコマンドにデータを渡したり、変数で出力を取得したりする場合、他にも多くの問題があります。
- 引数のサイズの制限(一部のテキストユーティリティの実装にも制限がありますが、到達した引数の効果は一般にそれほど問題ではありません)
- NUL文字(テキストユーティリティの問題でもあります)。
- オプションで始まる引数
-
(または+
時々)
- 一般的のようなものをループで使用される各種コマンドの様々な癖
expr
、test
...
- 一貫性のない方法でマルチバイト文字を処理するさまざまなシェルの(制限された)テキスト操作演算子。
- ...
セキュリティに関する考慮事項
コマンドのシェル変数と引数の操作を開始すると、地雷原に入ります。
あなたがいる場合、あなたの変数を引用することを忘れ、忘れオプションマーカーの終わりを、マルチバイト文字(標準これらの日)とのロケールで動作し、あなたは遅かれ早かれ脆弱性になりますバグを導入する特定のです。
ループを使用する場合。
未定
yes
ように高速ですか?