一般的なBashプログラミングエラー(続き)

私はコミュニティにBash Pitfallsの翻訳を紹介し続けています。
パート1
翻訳の最初の出版

11. catファイル| sed s / foo / bar />ファイル


同じパイプラインでファイルから読み取り、書き込むことはできません。 パイプラインの構築方法に応じて、ファイルをゼロにするか(または、オペレーティングシステムによってパイプラインに割り当てられたバッファーに等しいサイズに切り捨てられるか)、利用可能なすべてのディスクスペースを占有するか、到達するまで無限に大きくなる可能性がありますオペレーティングシステムまたはクォータなどで指定されたファイルのサイズの制限

ファイルの最後にデータを追加する以外にファイルを変更する場合は、中間点で一時ファイルを作成する必要があります。 例(このコードはすべてのシェルで機能します):

  sed 's / foo / bar / g' file> tmpfile && mv tmpfile file 

次のスニペットは、GNU sed 4.x以降を使用している場合にのみ機能します。

  sed -i 's / foo / bar / g'ファイル 

これも一時ファイルを作成してから名前を変更することに注意してください-気付かないだけです。

sedのBSDバージョンでは、ファイルのバックアップコピーに追加される拡張子を指定する必要があります。 スクリプトに自信がある場合は、拡張子ゼロを指定できます。

  sed -i '' 's / foo / bar / g'ファイル 

おそらくsed 4.xよりも一般的なperl 5.xも使用できます。

  perl -pi -e 's / foo / bar / g'ファイル 

ファイルのヒープ内の文字列を大量に置換するタスクのさまざまな側面については、 Bash FAQ#21で説明しています。

12. echo $ foo


この比較的無邪気な見た目のチームは、不快な結果を招く可能性があります。 変数$foo引用符で囲まれていないため、単語に分割されるだけでなく、それに含まれるテンプレートもそれに一致するファイルの名前に変換されます。 このため、bashプログラマーは、変数に正しい値が含まれていると誤って考えることがありますが、このechoコマンドはbashロジックに従って変数を表示し、誤解を招きます。

  MSG =「* .zip形式のファイル名を入力してください」
 echo $ msg 

このメッセージは単語に分割され、 *.zipなどのすべてのパターンが展開されます。 スクリプトのユーザーがフレーズを見たときにどう思うか:

  freenfss.zip lw35nfss.zip形式のファイル名を入力してください 

別の例を次に示します。

  VAR = *。Zip#VARには、アスタリスク、ドット、および「zip」という単語が含まれます
 echo "$ VAR"#は* .zipを出力します
 echo $ VAR#は、名前が.zipで終わるファイルをリストします 

実際、echoコマンドはまったく安全に使用できません。 変数に「-n」文字が2つしか含まれていない場合、 echoコマンドはそれらをオプションとして見なし、印刷する必要のあるデータとしてではなく、何も出力しません。 変数の値を印刷する唯一の信頼できる方法は、 printfコマンドを使用することです。
printf "%s\n" "$foo"

13. $ foo = bar


いいえ、名前の先頭に「$」を付けて変数を作成することはできません。 これはPerlではありません。 書くだけで十分です:

  foo = bar 

14. foo = bar


いいえ、変数に値を割り当てて「=」の前後にスペースを残すことはできません。 これはCではありませんfoo = barと記述すると、シェルはこれを3つの単語に分割します。最初のfooはコマンドの名前として、残りの2つは引数として使用されます。

同じ理由で、次の式も間違っています。

  foo = bar#間違っています!
 foo = bar#間違っています!
 $ foo = bar#完全に間違っています! 

  foo = bar#そうです。 

15.エコー<< EOF


埋め込みドキュメントは、テキストデータの大きなブロックをスクリプトに埋め込むのに役立ちます。 インタプリタは、同様の構造に遭遇すると、指定されたマーカー(この場合はEOF )までの行をコマンドの入力ストリームに送ります。 残念ながら、エコーはSTDINからのデータを受け入れません。

  #間違った:
エコー<< EOF
ハローワールド
 Eof 

  #正解:
猫<< EOF
ハローワールド
 Eof 

16. su -c 'いくつかのコマンド'


Linuxでは、この構文は正しく、エラーは発生しません。 問題は、一部のシステム(FreeBSDやSolarisなど)では、 suコマンドの-c引数の目的がまったく異なることです。 特に、FreeBSDでは、 -cスイッチは、コマンドの実行時に制限が適用されるクラスを示します。シェル引数は、ターゲットユーザー名の後に指定する必要があります。 ユーザー名がない場合、 -cオプションは新しいシェルではなくsuコマンドに適用されます。 したがって、システム(スクリプトが実行されるプラットフォームを知っている人)に関係なく、常にターゲットユーザーの名前を指定することをお勧めします。

  su root -c 'some command'#正しい。 

17. cd / foo; バー


エラーが発生した場合にcdの結果を確認しないと、開発者が意図した間違ったディレクトリでbarコマンドが実行される可能性があります。 barrm *ようなものが含まれている場合、これは災害につながる可能性がありbar

したがって、cdコマンドの戻りコードを常に確認する必要があります。 最も簡単な方法:

  cd / foo && bar 

cdの後に複数のコマンドが続く場合、次のように記述できます。

  cd / foo ||  1番出口
バー
バズ
バット...#多くのチーム。 

cdは、 bash: cd: /foo: No such file or directoryの形式のstderrのメッセージとともに、ディレクトリ変更エラーを報告しbash: cd: /foo: No such file or directory 。 エラーメッセージを標準出力に表示する場合は、コマンドのグループ化を使用する必要があります。

  cd / net ||  {echo "/ netを読み取れません。Sambaネットワークにログインしていることを確認して、再試行してください。"; 出口1;  }
 do_stuff
 more_stuff 

{echo間のスペース、および終了の前のセミコロン}注意してください。

スクリプトの先頭にset -eコマンドを追加して、ゼロ以外の値を返す各コマンドの後にスクリプトが中断されるようにする人もいますが、多くの一般的なコマンドは単純な警告としてゼロ以外の値を返すことがあるため、このトリックは慎重に使用する必要があります。そのようなエラーを重大と見なす必要はありません。

ちなみに、bashスクリプトのディレクトリで多くの作業を行う場合は、 pushdpopdおよびdirsコマンドに関連する場所でman bash再読み取りしてpushd 。 おそらくcdpwd詰めたすべてのコードは単に必要ありません:)。

羊に戻りましょう。 このスニペットを比較してください:

  find ... -type d | サブディレクトリを読み取り中; する
     cd "$ subdir" &&何でも&& ... && cd-
やった 

これで:

  find ... -type d | サブディレクトリを読み取り中; する
     (cd "$ subdir" &&何でも&& ...)
やった 

サブシェルを強制すると、cdおよび後続のコマンドが呼び出され、サブシェルで実行されます。 サイクルの次の反復では、ディレクトリの変更が成功したか、エラーで終了したかに関係なく、最初の場所に戻ります。 手動で戻る必要はありません。

さらに、最後から2番目の例には別のエラーが含まれています。いずれかのコマンドが失敗した場合、最初のディレクトリに戻ることはできません。 サブシェルを使用せずにこれを修正するには、各反復の最後にcd "$ORIGINAL_DIR"などの操作を行う必要があります。これにより、スクリプトに少し混乱が生じます。

18. [bar == "$ foo"]


==演算子は[引数ではありません。 代わりに=を使用するか、 [キーワード[[置き換えます:

  [bar = "$ foo"] && echo yes
 [[bar == $ foo]] && echo yes 

19. for i for {1..10}; do ./something&; やった


セミコロン「;」は使用できません。 &の直後。 この余分な文字を削除するだけです:

  {1..10}のi する./something&done 

&記号自体は、「;」のようにコマンドの終わりを示す記号です。 および改行。 それらを次々と置くことはできません。

20. cmd1 && cmd2 || cmd3


多くの人は&&および||を使用することを好みます if ... then ... else ... fi省略形として。 場合によっては、絶対に安全です。

  [[-s $ errorlog]] && echo "おお、なんらかのエラーがありました。"  ||  echo "成功しました。" 

ただし、一般的な場合、 &&前のcmd2コマンドも戻りコードを生成でき、このコードが0でない場合、||に続くコマンドが実行されるため、この構成はif ... fi完全な同等物として機能できません。 多くの人をst迷の状態に導くことができる簡単な例:

  i = 0
 true &&((i ++))||  ((i--))
 echo $ i#は0を出力します 

ここで何が起こったのですか? 原則として、変数iの値は1にする必要がありますが、スクリプトの最後には0が含まれます。つまり、i ++コマンドとi--コマンドの両方が順番に実行されます。 コマンド((i ++))は、Cスタイルの括弧で式を実行した結果の数値を返します。この式の値は0(初期値はi)ですが、Cでは整数値0の式は偽と見なされます。 したがって、式((i ++))(iは0)は1(false)を返し、コマンド((i--))が実行されます。

プリインクリメント演算子を使用した場合、これは発生しません。この場合、リターンコード++ iはtrueであるためです。

  i = 0
 true &&((++ i))||  ((--i))
 echo $ i#印刷1 

しかし、私たちは幸運であり、コードは状況の「ランダムな」組み合わせに対してのみ機能します。 したがって、 x && y || z依存することはできません。 x && y || zyがfalseを返す可能性がわずかでもある場合(iが0ではなく-1の場合、最後のコードフラグメントがエラーで実行されます)

セキュリティが必要な場合、またはコードを機能させるメカニズムが疑わしい場合、または前の段落で何も理解していないif ... fiは、スクリプトでif ... fiないように記述してください。

  i = 0
真の場合; それから
     ((i ++))
他に
     ((i--))
 fi
 echo $ i#は1を出力します。 

Bourneシェルもこれに適用されます。

 #コマンドの両方のブロックが実行されます:
 $ true && {echo true; 偽  } ||  {echo false; 本当  }
本当
偽 

21. UTF-8およびBOM(バイトオーダーマーク、バイトオーダーマーク)について


一般的に、Unixでは、UTF-8でエンコードされたテキストはバイト順マークを使用しません。 テキストのエンコードは、ロケール、ファイルのMIMEタイプ、またはその他のメタデータによって決定されます。 BOMが存在しても人間の可読性の観点からUTF-8文書を損なうことはありませんが、スクリプト、ソースコード、構成ファイルなどのファイルの自動解釈で問題が発生する可能性があります。 BOMで始まるファイルは、DOS改行を含むファイルと同様に、外部として扱われる必要があります。

シェルスクリプト:「8ビット環境でUTF-8を透過的に使用できる場合、BOMは、 #!などのストリームの先頭にASCII文字があることを前提とするプロトコルまたはファイル形式と交差します#! Unixシェルスクリプトの冒頭» http://unicode.org/faq/utf_bom.html#bom5

Source: https://habr.com/ru/post/J47915/


All Articles