落とし穴バッシュ





この記事では、Bashプログラマーによるエラーについて説明します。 上記の例にはすべていくつかの欠陥があります。 常に引用符を使用し、単語分割を使用しない場合は、以下で説明するエラーの多くを回避できます! ワードブレイキングは、Bourneシェルから継承された欠陥のあるレガシープラクティスです。 展開を引用符で囲まない場合、デフォルトで使用されます。 一般に、落とし穴の大部分は引用符なしの置換に何らかの形で関係しており、単語の分割と結果のグロビングにつながります。



内容
  1. for i in $(ls * .mp3)
  2. cp $ file $ターゲット
  3. 先行するダッシュファイル名
  4. [$ foo = "bar"]
  5. cd $(ディレクトリ名「$ f」)
  6. ["$ foo" = bar && "$ bar" = foo]
  7. [[$ foo> 7]]
  8. grep foo bar | 読み取り中-r; do((count ++)); やった
  9. if [grep foo myfile]
  10. if [bar = "$ foo"]; その後...
  11. if [[a = b] && [c = d]]; その後...
  12. fooを読む
  13. catファイル| sed s / foo / bar />ファイル
  14. echo $ foo
  15. $ foo = bar
  16. foo = bar
  17. エコー<< EOF
  18. su -c 'いくつかのコマンド'
  19. cd / foo; バー
  20. [バー== "$ foo"]
  21. {1..10}のi do ./something&; やった
  22. cmd1 && cmd2 || cmd3
  23. echo "Hello World!"
  24. arg for $ *
  25. 関数foo()
  26. エコー「〜」
  27. local varname = $(コマンド)
  28. export foo =〜/ bar
  29. sed 's / $ foo /さようなら/'
  30. tr [AZ] [az]
  31. ps ax | grep gedit
  32. printf "$ foo"
  33. {1 .. $ n}のi
  34. if [[$ foo = $ bar]](目的に応じて)
  35. if [[$ foo =〜 'some RE']]
  36. [-n $ foo]または[-z $ foo]
  37. [[-e "$ broken_symlink"]]は、$ broken_symlinkの存在にもかかわらず1を返します
  38. Edファイルがクラッシュ<<< "g / d \ {0.3 \} / s // e / g"
  39. サブストリングexprが一致に失敗しました
  40. UTF-8およびバイトオーダーマーク、BOMについて
  41. 内容= $(<ファイル)
  42. ./*のファイルの場合。 if [[$ file!= *。*]]
  43. somecmd 2>&1 >>ログファイル
  44. cmd; ((!$?))|| 死ぬ
  45. y = $((配列[$ x]))
  46. numを読み取ります。 エコー$((num + 1))
  47. IFS =、読み取り-raフィールド<<< "$ csv_line"
  48. export CDPATH =。:〜/ myProject




1. $のi(ls * .mp3)



BASHプログラマーが犯す最も一般的な間違いの1つ。 そのようなサイクルを書くことで表現されます:



for i in $(ls *.mp3); do # ! some command $i # ! done for i in $(ls) # ! for i in `ls` # ! for i in $(find . -type f) # ! for i in `find . -type f` # ! files=($(find . -type f)) # ! for i in ${files[@]} # !
      
      





はい、 ls



の出力を処理したり、ファイル名のリストとしてfind



して反復したりfind



と便利です。 しかし、 できません 。 このアプローチは完全に誤りであり、修正する方法はありません。 これに完全に異なるアプローチをする必要があります。



ここには少なくとも5つの問題があります。



  1. ファイル名にスペースが含まれている場合、 WordSplittingの対象となります。 現在のフォルダに01 - Don't Eat the Yellow Snow.mp3



    という名前のファイルがあるとします。 for



    ループは各単語を反復処理し、01、-、Do n't、Eatなどの結果を生成します。



  2. ファイル名にグロブ文字が含まれている場合、グロビング(「 グロビング 」)が行われます。 出力ls



    に文字*が含まれている場合、それが属する単語はテンプレートと見なされ、それに対応するすべてのファイル名のリストに置き換えられます。 ファイルパスには、NUL以外の任意の文字を含めることができます。 はい、改行を含む。



  3. ls



    ユーティリティはファイル名を破損する可能性があります。 作業するプラットフォーム、使用する(または使用しない)引数、および標準出力が端末を示しているかどうかによって、 ls



    はファイル名の一部の文字を突然「?」に置き換える場合があります。 または、まったく表示しないようにします。 ls出力を解析しようとしないでください



  4. CommandSubstitutionは、出力からすべての末尾の改行を切り捨てます。 一見すると、 ls



    は新しい行を追加するため、これは良いことです。 しかし、リストの最後のファイル名が新しい行で終わる場合、 `...`または$()



    は削除され、追加されます。


また、二重引用符を囲むことはできません。



 for i in "$(ls *.mp3)"; do # !
      
      





これにより、出力ls



全体が1つの単語と見なされます。 各ファイル名を反復処理する代わりに、ループが1回実行され、結合されたファイル名から文字列値をi



に割り当てます。 また、 IFSを新しい行に変更することはできません。 ファイル名には改行も含まれる場合があります。



このトピックのもう1つのバリエーションは、ワードブレークの誤用と、ファイルの行を(誤って)読み取るforループです。 例:



 IFS=$'\n' for line in $(cat file); do ... # !
      
      





これは機能しません! 特に文字列がファイル名の場合。 Bash(Bourneファミリーの他のシェルと同様)は、そのようには機能しません。 上記のすべてに加えて、 ls



自体を使用する必要はまったくありません。 これは外部コマンドであり、その出力は人間が読むために特別に設計されており、スクリプトの解析用ではありません。



それで、あなたはどうやってそれを正しくしますか?



たとえば、 find



-exec



と組み合わせて使用​​しfind







 find . -type f -exec some command {} \;
      
      





ls



代わりにls



このオプションを検討できます。



 for i in *.mp3; do #  ! ... some command "$i" # ...    ! done
      
      





BashのようなPOSIXシェルには、特にこのためのグロビングプロパティがあります。これにより、マップされたファイル名のリストにテンプレートを適用できます。 外部ユーティリティの結果を解釈する必要はありません。 グロビングは置換手順の最後のステップであるため、引用符なしの置換の影響を受けない個々の単語に*.mp3



パターンが正しく適用されます。 ファイルを再帰的に処理する必要がある場合は、 UsingFindを使用するか、Bash 4 shopt -s globstar



詳しく見てください。



質問: * .mp3パターンに一致するファイルが現在のフォルダーにない場合はどうなりますか? for



ループはi="*.mp3"



で1回実行されますが、これは予期される動作ではありません! この問題の解決策として、適切なファイルの存在の確認を使用できます。



 # POSIX for i in *.mp3; do [ -e "$i" ] || continue some command "$i" done
      
      





別の解決策は、 shopt -s nullglob



Bashプロパティを使用することshopt -s nullglob



。 ただし、これはドキュメントを読んで、このスクリプトの他のすべてのグローブに対するこの設定の影響を慎重に評価した後にのみ行うことができます。 ループ本体の$i



囲む引用符に注意してください。 これは、2番目の問題につながります。



2. cp $ file $ターゲット



このチームの何が問題になっていますか? 基本的に、 $file



$target



スペースやワイルドカードが含まれていないことが事前にわかっていれば問題ありません。 ただし、置換の結果は引き続きWordSplittingとファイルパスの置換の対象となります。 したがって、パラメーターの展開は常に二重引用符で囲みます。



 cp -- "$file" "$target"
      
      





それ以外の場合は、 cp 01 - Don't Eat the Yellow Snow.mp3 /mnt/usb



cp: cannot stat `01': No such file or directory



cp 01 - Don't Eat the Yellow Snow.mp3 /mnt/usb



cp: cannot stat `01': No such file or directory



ようなエラーになりcp: cannot stat `01': No such file or directory



$file



にワイルドカード文字( *またはまたは[ )が含まれる場合、一致するファイルがある場合にのみ展開されます。 二重引用符を使用すると、「$ファイル」の先頭に「-」が現れるまですべて問題ありません。 この場合、 cp



はコマンドラインオプションをフィードしようとしていると判断します(次の章を参照)。



いくつかの異常な状況でも、変数の内容を保証できる場合、特にファイル名が含まれている場合、パラメーターを引用符で囲むことは適切で一般的な方法です。 経験豊富なスクリプト作成者は常に引用符を使用します。ただし、まれに、コードコンテキストからパラメーターに保証された安全な値が含まれていることが明らかな場合を除きます。 専門家は、おそらくcp



コマンドヘッダーの使用は間違いであると判断するでしょう。 あなたもそう思うべきです。



3.前にハイフンが付いたファイル名



ハイフンが前に付いたファイル名は、多くの問題を引き起こす可能性があります。 *.mp3



ようなグローブ*.mp3



(現在のロケールに応じて)拡張リストにソートされ、ほとんどのロケールではハイフンが最初にソートされ、次に文字がソートされます。 次に、リストが-filename



をオプションとして誤って解釈する可能性のあるコマンドに渡されます。 この状況には2つの主な解決策があります。



1つ目は、コマンド(例: cp



)とその引数の間に2つのハイフン( --



)を挿入することです。 これはオプションの検索を停止するためのシグナルとなり、すべてが正常になります。



 cp -- "$file" "$target"
      
      





しかし、このアプローチには問題があります。 必ず挿入する必要があります --



コンテキストでパラメーター使用するたびに 、オプションとして解釈できる場合。 そして、これは多くの冗長性を意味し、簡単に何かを見逃す可能性があります。



解析オプション用の適切に作成されたライブラリのほとんどはこれを理解しており、正しく使用されるプログラムはこの機能を無料で継承する必要があります。 ただし、オプションの終わりを認識する責任はアプリケーションにのみあることに留意してください。 オプションを手動で解析するプログラム、誤って解析するプログラム、またはサードパーティのライブラリを使用するプログラムでは、末尾が認識されない場合があります。 POSIXで説明されているいくつかの例外を除き、標準ユーティリティはこれ行う必要があります。 たとえば、 echo







別のオプションは、ファイル名が常にフォルダーで始まることを確認することです。 このために、相対または絶対ファイルパスが使用されます。



 for i in ./*.mp3; do cp "$i" /target ... done
      
      





この場合、名前がハイフンで始まるファイルがある場合でも、globのおかげで、変数には常に./-foo.mp3



ようなものが含まれることを確認できます。 そして、これはcp



に関しては完全に安全です。



最後に、すべての結果に同じ接頭辞が付き、変数がループの本体で数回しか使用されないことを保証できる場合、置換時に接頭辞を単純に連結できます。 理論的には、各単語のいくつかの余分な文字の生成と保存が節約されます。



 for i in *.mp3; do cp "./$i" /target ... done
      
      





4. [$ foo = "bar"]



この状況は、第2章で説明した問題に非常に似ています。 しかし、それでも非常に重要なので繰り返します。 ヘッダー行の引用符は、必要な場所ではありません。 Bashでは、文字列リテラルを引用する必要はありません (メタ文字またはパターン文字が含まれている場合を除く)。 ただし、変数にスペースまたはワイルドカードを含めることができる場合は、変数を引用符で囲む必要あります。



この例は、いくつかの理由で壊れる可能性があります。





Bashや他の多くのkshのようなシェルには、 [[を使用した優れた代替手段があります。



 # Bash / Ksh [[ $foo == bar ]] # !
      
      





=



[[ ]]



の左側にある変数は、単語の分割やグロビングの影響を受けないため、引用符で囲む必要はありません。 また、空の変数も正しく処理されます。 一方、引用符を使用しても害はありません。 [



およびtest



とは異なり、 ==



も使用できます。 [[



を使用して比較する場合、単純な文字列比較ではなく、右側の文字列に対してパターンマッチングが実行されることに注意してください。 正しい文字列をリテラルにするには、パターンマッチングのコンテキストで特別な意味を持つ文字を使用するときに引用符で囲む必要があります。



 # Bash / Ksh match=b*r [[ $foo == "$match" ]] # !    ,       b*r.
      
      





あなたはおそらく同様のコードを見てきました:



 # POSIX / Bourne [ x"$foo" = xbar ] # ,    .
      
      





非常に古いシェルで実行されるコードの場合、ハックx " $foo



"が必要です。 ここで、 [[



より原始的な[



$foo



がハイフンで始まる場合、混乱が生じます。 古いシステムでは、 [



=



の右側のトークンがハイフンで始まるかどうかは気にしません。 彼女はそれを文字通り使用しています。 そのため、左側に注意する必要があります。



この回避策を必要とするシェルはPOSIX互換ではないことに注意してください。 Heirloom Bourneでさえこれを必要としません(おそらくこれは、最も一般的なシステムシェルの1つであるBourneシェルのPOSIXクローンではない可能性があります)。 このような極端な移植性が要求されることはめったになく、コードが読みにくくなり、美しくなります。



5. cd $(ディレクトリ名「$ f」)



引用符に関連する別のエラー。 変数展開の場合と同様に、コマンド置換の結果は、単語とファイルパスの置換に分割されます。 したがって、引用符で囲みます。



 cd -P -- "$(dirname -- "$f")"
      
      





引用符のネストの背後にあるロジックは、ここでは完全に明らかではありません。 Cプログラマーは、1番目と2番目の二重引用符がグループ化され、3番目と4番目の二重引用符が結合されることを期待します。 しかし、Bashでは、すべてが異なります。 Bashは、コマンド置換内の二重引用符を1つのペアとして扱い、置換外の二重引用符を別のペアとして扱います。



別の方法で記述することもできます。パーサーはコマンド置換を「ネストレベル」として扱い、内部の引用符は外部の引用符とは別に移動します。



6. ["$ foo" = bar && "$ bar" = foo]



古いテスト(または[)コマンド内では&&



を使用できません。 Bashパーサーは、 [[ ]]



または(( ))



外側で&&



を検出し、その結果、コマンドを&&



前後で2つのチームに分割します。 代わりに、次の2つのオプションのいずれかを使用します。



 [ bar = "$foo" ] && [ foo = "$bar" ] # ! (POSIX) [[ $foo = bar && $bar = foo ]] #  ! (Bash / Ksh)
      
      





(第4章で述べた遺産のために、定数と変数を[



内で交換しました。また、 [[



交換することもできますが、テンプレートとして解釈しないようにするには、引用符を使用する必要があります)



同じことが||



も当てはまります 。 [[



または2つのコマンド[



使用します。



これを避けてください:



 [ bar = "$foo" -a foo = "$bar" ] #  .
      
      





バイナリ演算子-a



-o



および(/)(グループ化)は、POSIX標準に対するXSI拡張です。 POSIX-2008では、これらはすべて非推奨としてマークされています。 新しいコードでそれらを使用する価値はありません。 [ A = B -a C = D ]



(または-o



)に関連する実際的な問題の1つは、 POSIXが test



結果を決定しないこと、または4つ以上の引数を使用することです。 これはおそらくほとんどのシェルで機能しますが、期待することはできません。 POSIXシェル用に作成する必要がある場合は、 &&



演算子で区切られた2つのtest



または[



コマンドを使用します。



7. [[$ foo> 7]]



ここにはいくつかのポイントがあります。 まず、 [[



は算術式を計算するためだけに使用すべきではありません。 サポートされているテストステートメントの1つを含むテスト式に使用する必要があります。 技術的には[[



演算子を使用して計算を実行できます 、式のどこかに存在する非数学的なテスト演算子の1つと組み合わせてこれを行うことは意味があります。 数値データを比較するだけの場合(または他の算術演算を実行する場合)は、 (( ))



を使用する方がはるかに優れています。



 # Bash / Ksh ((foo > 7)) # ! [[ foo -gt 7 ]] # ,  .   .   ((...))  let.
      
      





[[ ]]



内で>演算子を使用すると、システムはこれを数値ではなく、文字列データの比較(ロケールによるソート順のチェック)として扱います。 時々これは機能するかもしれませんが、それはあなたがそれを最も期待していない時にちょうどあなたを失敗させます。 さらに悪いことに、use> inside [ ]



:これは出力のリダイレクトです。 7という名前のファイルがフォルダーに表示され、 $foo



何かがあるまでテストは正常に実行されます。



POSIXとの厳密な互換性が必要で、コマンド((



、その後、昔ながらの[



::



 # POSIX [ "$foo" -gt 7 ] #  ! [ $((foo > 7)) -ne 0 ] #   POSIX  ((     .
      
      





$foo



整数でない場合、 test ... -gt



は失敗することに注意してください。 したがって、引用符で囲むことはパフォーマンスのためだけに意味があり、一部のシェルで副作用の可能性を減らすために引数を単一の単語に分割します。



算術コンテキスト( ((



またはlet



)を含む)または数値比較を含むテスト式[



の入力を保証できない場合、計算を実行する前に常に入力を検証する必要あります。



 # POSIX case $foo in *[![:digit:]]*) printf '$foo expanded to a non-digit: %s\n' "$foo" >&2 exit 1 ;; *) [ $foo -gt 7 ] esac
      
      





8. grep foo bar | 読み取り中-r; do((count ++)); やった



このコードは正常に見えますか? もちろん、これはgrep -c



単なる平凡な実装ですが、これは単純にするために行われています。 各パイプラインコマンドは個別のサブシェルで実行されるため、 count



変更はwhile



境界を越えて伝播しません。 ある時点で、これはBashの初心者を驚かせます。



POSIXは、パイプラインの最後の要素をサブシェルで評価する必要があるかどうかを判断しません。 shopt -s lastpipe



いるksh93やBash> = 4.2などの一部のシェルは、サンプルのシェルプロセスでwhile



を実行し、副作用を引き起こす可能性があります。 したがって、ポータブルスクリプトは、このような動作に依存しないように作成する必要があります。



この問題や同様の問題を解決する方法は、 Bash FAQ#24から見つけることができます。 ここで説明するには長すぎます。



9. if [grep foo myfile]



多くの初心者は、このキーワードの直後に[



または[[



続くことが多いため、 if



式について誤解しています。 Cのif



で使用される単純な括弧のように、人々は[



何らかの理由でif



構文の一部であると考えます。それは真実ではありません! コマンドを取得したif



。 それは[



、それはif



構文マーカーではない。 このコマンドは、最後の引数が]



なければならないことを除いて、 test



と同等です。 例:



 # POSIX if [ false ]; then echo "HELP"; fi if test false; then echo "HELP"; fi
      
      





これらの行は同等です。どちらも、false引数が空でないことを確認します。 どちらの場合も、HELPが出力されます。他の言語から来て、シェル構文を理解しようとしているプログラマーの驚きです。



if



構文if



次のとおりです。



 if COMMANDS then <COMMANDS> elif <COMMANDS> # optional then <COMMANDS> else <COMMANDS> # optional fi # required
      
      





もう一度- [



はチームです。 彼女は他の普通のチームのように議論を受け取ります。 if



は、他のコマンドを含む複合コマンドです。 そしてその構文は[



Bashには組み込みコマンド[



があります。したがって、 [



]



については何も特別なことはありません。 Bashはコマンドへの引数として]



を渡すだけであり、コマンドは最後の引数である必要があります。そうでない場合、スクリプトは見苦しくなります。



0個以上のオプションのelif



セクションと、1つのオプションのelse



セクションがある場合がありelse







複合if



コマンドには、コマンドのリストが配置される2つ以上のセクションが含まれます。 各セクションは、キーワードthen



elif



またはelse



で始まり、キーワードfi



終わります。 最初のセクションと後続の各elif



セクションの最後のコマンドの完了コードは、対応する各then



セクションの計算を決定します。 他のelif



セクションは、 then



1つが実行される前に評価されます。 then



セクションが計算されない場合、 else



ブランチへの切り替えが発生します。 else



がない場合、 if



ブロックは完了し、結果のif



コマンドは0(true)を返します。



grep



出力に応じて決定を行いたい場合は、括弧や角括弧、バックティック、またはその他の構文で囲む必要はありません! if



後にコマンドとしてgrep



を使用します。



 if grep -q fooregex myfile; then ... fi
      
      





grep



myfile



の文字列で一致を検出した場合、完了コードは0(true)になり、 then



部分が実行されます。 一致するものが見つからない場合、 grep



は0以外の値を返し、結果のif



コマンドはゼロになります。



また読む:





10. if [bar = "$ foo"]; その後...



 [bar="$foo"] # ! [ bar="$foo" ] #   !
      
      





前の章で説明したように、 [



はコマンドです(これはtype -t [



またはwhence -v [



type -t [



で証明できます。 他の単純なコマンドと同様に、Bashはスペースがそれに続くことを期待し、次に最初の引数、スペースをもう一度、などと期待します。 スペースを無視することはできません! 正しいスペルは次のとおりです。



 if [ bar = "$foo" ]; then ...
      
      





各コンポーネント( bar



=



、置換「 $foo



」、および]



)は、 [



。 , , .



11. if [ [ a = b ] && [ c = d ] ]; then ...



. [



. , if



- «», . [



. - if



Bash-, !



, :



 if [ a = b ] && [ c = d ]; then ...
      
      





, if



, &&



( AND, ). , :



 if test a = b && test c = d; then ...
      
      





test



false, if



. true, test



; true, if



. (C- &&



. Bash . , ||



OR .)



[[



&&



, :



 if [[ a = b && c = d ]]; then ...
      
      





6 , test



.



12. read $foo



$



read



. foo



, :



 read foo
      
      





:



 IFS= read -r foo
      
      





read $foo



/ $foo



. , foo



; .



13. cat file | sed s/foo/bar/ > file



. , , :





, , .



 printf %s\\n ',s/foo/bar/g' wq | ed -s file
      
      





, (*) .



:



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





GNU sed 4.x:



 sed -i 's/foo/bar/g' file(s)
      
      





, — .



- Perl 5.x (, , , GNU sed 4.x):



 perl -pi -e 's/foo/bar/g' file(s)
      
      





Bash FAQ #21 .



(*) sponge



moreutils :



 sed '...' file | grep '...' | sponge file
      
      





mv



, «» ( !) , file



. , , , .



+ mv



/ . , mv



sync



.



14. echo $foo



. $foo



, , . - Bash- , , . .



 msg=",    *.zip" echo $msg
      
      





, (glob) ( *.zip) . , : , freenfss.zip lw35nfss.zip



. イラスト:



 var="*.zip" # var  ,    "zip" echo "$var" #  *.zip echo $var #   ,   .zip
      
      





, echo



. , , -n



, echo



, , . — printf



:



 printf "%s\n" "$foo"
      
      





15. $foo=bar



, $



, . Perl.



16. foo = bar



, =



, . . foo = bar



, . — foo



— . — .





17. echo <<EOF



Here- — . . , echo



stdin.



 #  : echo <<EOF Hello world How's it going? EOF #     : cat <<EOF Hello world How's it going? EOF #   ,      (, echo ): echo "Hello world How's it going?"
      
      





. . . , , cat



, :



 #   printf ( , printf ): printf %s "\ Hello world How's it going? "
      
      





printf



, \ . ( ). \n



printf



. \ . , , «» :



 printf %s \ 'Hello world ' printf %s 'Hello world '
      
      





18. su -c 'some command'



. , su



-c



, , . OpenBSD:



 $ su -c 'echo hello' su: only the superuser may specify a login class
      
      





-c 'some command'



, -c



.



 su root -c 'some command' # Now it's right.
      
      





su



root-, . , . .



19. cd /foo; bar



cd



, bar



. , , , bar



rm -f *



. cd



. :



 cd /foo && bar
      
      





cd



, :



 cd /foo || exit 1 bar baz bat ... # Lots of commands.
      
      





cd



, stderr- «bash: cd: /foo: No such file or directory». stdout , :



 cd /net || { echo >&2 "Can't read /net. Make sure you've logged in to the Samba network, and try again."; xit 1; } do_stuff more_stuff
      
      





, {



echo



. }



;







set -e , , , . ( , ).



, Bash-, pushd



, popd



dirs



. , , cd



pwd



, . :



 find ... -type d -print0 | while IFS= read -r -d '' subdir; do here=$PWD cd "$subdir" && whatever && ... cd "$here" done
      
      





C :



 find ... -type d -print0 | while IFS= read -r -d '' subdir; do (cd "$subdir" || exit; whatever; ...) done
      
      





cd



. , , cd



. , ... && ...



, . ( ).



20. [ bar == "$foo" ]



==



POSIX- [



. =



[[



.



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





Bash [ "$x" == y ]



, . — «» ( Bashism ). , [[



.



21. for i in {1..10}; do ./something &; done



;



&



. ;







  for i in {1..10}; do ./something & done : for i in {1..10}; do ./something & done
      
      





&



(command terminator), ;



。 .



, ;



, ;







22. cmd1 && cmd2 || cmd3



- &&



||



if ... then ... else ... fi



. :



 [[ -s $errorlog ]] && echo "Uh oh, there were some errors." || echo "Successful."
      
      





if ... fi



. , &&



, . «true» (0), , ||



。 例:



 i=0 true && ((i++)) || ((i--)) echo $i # Prints 0
      
      





ここで何が起こっていますか? , i



1, 0. ? i++



, i--



. ((i++))



, . 0 ( i



), , 0, false . ((i++))



( i



0) 1 (false), ((i--))



.



, , ++i



true:



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





. x && y || z



, y ! , i



-1 0.



, , - , , if ... fi



.



 i=0 if true; then ((i++)) else ((i--)) fi echo $i # Prints 1
      
      





Bourne:



 true && { echo true; false; } || { echo false; true; }
      
      





«true» «false», «true».



23. echo «Hello World!»



Bash ( 4.3), :



 bash: !": event not found
      
      





, , Bash (history expansion) csh, . , . , «» :



 $ echo "hi\!" hi\!
      
      





histexpand



. set +H



set +o histexpand



:



: histexpand



, ?



,



 mp3info -t "Don't Let It Show" ... mp3info -t "Ah! Leah!" ...
      
      





, . . , , . . , ~/.bashrc. --



GreyCat



:



 echo 'Hello World!'
      
      





または



 set +H echo "Hello World!"
      
      





または



 histchars=
      
      





set +H



set +o histexpand



~/.bashrc



, . , , .



:



 exmark='!' echo "Hello, world$exmark"
      
      





Bash 4.3 , !



. , echo "Hello World!"



, :



 echo "Hello, World!(and the rest of the Universe)" echo "foo!'bar'"
      
      





24. for arg in $*



Bash ( Bourne-) , . $*



, $@



. , . :



 for arg in "$@" #  : for arg
      
      





, for arg



for arg in "$@"



. "$@"



— , ( ). 99% .



例:



 #   for x in $*; do echo "parameter: '$x'" done $ ./myscript 'arg 1' arg2 arg3 parameter: 'arg' parameter: '1' parameter: 'arg2' parameter: 'arg3'
      
      





:



 #   for x in "$@"; do echo "parameter: '$x'" done #  : for x; do echo "parameter: '$x'" done $ ./myscript 'arg 1' arg2 arg3 parameter: 'arg 1' parameter: 'arg2' parameter: 'arg3'
      
      





25. function foo()



, . function



()



. Bash ( ) . (, zsh 4.x , , ). function foo



, :



 foo() { ... }
      
      





26. echo "~"



, '~' . echo stdout '~', . , , $HOME, '~'. , $HOME — "/home/my photos".



 "~/dir with spaces" #   "~/dir with spaces" ~"/dir with spaces" #   "~/dir with spaces" ~/"dir with spaces" #   "/home/my photos/dir with spaces" "$HOME/dir with spaces" #   "/home/my photos/dir with spaces"
      
      





27. local varname=$(command)



, local



. . , ( $?



) , . . :



 local varname varname=$(command) rc=$?
      
      





.



28. export foo=~/bar



, — — ( ). , =



.



export



local



. ( Bash) export foo=~/bar



, ( dash) — .



 foo=~/bar; export foo # ! export foo="$HOME/bar" # !
      
      





29. sed 's/$foo/good bye/'



$foo



. — $



. :



 foo="hello"; sed "s/$foo/good bye/"
      
      





: escapes. « ».



30. tr [AZ] [az]



. : [AZ]



[az]



. , , . , . , 3 .



: tr



. , '[' '[', - AZ az, ']' ']'. , .



, , AZ az 26 ASCII-. z ! , :



 # ,    26   LC_COLLATE=C tr AZ az # ,        .       tr '[:upper:]' '[:lower:]'
      
      





, .



31. ps ax | grep gedit



, . gedit. - , gedit ( ). , . PID gedit (),



 $ ps ax | grep gedit 10530 ? S 6:23 gedit 32118 pts/0 R+ 0:00 grep gedit
      
      





, Race Condition , grep. :



 ps ax | grep -v grep | grep gedit # ,   
      
      







:



 ps ax | grep '[g]edit' #   ,   shell GLOB
      
      





Grep , [g]edit



, grep



gedit



.



GNU/Linux –C :



 $ ps -C gedit PID TTY TIME CMD 10530 ? 00:06:23 gedit
      
      





, pgrep



?



 $ pgrep gedit 10530
      
      





PID awk



cut



:



 $ ps -C gedit | awk '{print $1}' | tail -n1
      
      





ps



:



 $ ps -C gedit -opid= 10530
      
      





1992 pgrep



, , pidof



( GNU/Linux):



 $ pidof gedit 10530
      
      





PID, , pkill



. , , , pgrep/pkill ssh



sshd, .



, . , Firefox firefox-bin. , , ps ax | grep firefox . pgrep:



 $ pgrep -fl firefox 3128 /usr/lib/firefox/firefox 7120 /usr/lib/firefox/plugin-container /usr/lib/flashplugin-installer/libflashplayer.so -greomni /usr/lib/firefox/omni.ja 3128 true plugin
      
      





. 真剣に。



32. printf "$foo"



, . $foo



, \



%



. :



 printf %s "$foo" printf '%s\n' "$foo"
      
      





33. for i in {1..$n}



Bash . $n



, , . , runtime. :



 for ((i=1; i<=n; i++)); do ... done
      
      





for



, , (pre-expands), .



34. if [[ $foo = $bar ]] ( )



, =



[[, Bash , . bar



*, true. , :



 if [[ $foo = "$bar" ]]
      
      





, , , . .



, =~



, , . .



35. if [[ $foo =~ 'some RE' ]]



, . , , :



 re='some RE' if [[ $foo =~ $re ]]
      
      





=~



Bash. .



[[



:



 [[ $foo = "*.glob" ]] # Wrong! *.glob is treated as a literal string. [[ $foo = *.glob ]] # Correct. *.glob is treated as a glob-style pattern.
      
      





36. [ -n $foo ] or [ -z $foo ]



[



, . $foo



0 , 42 , , 1, .



 [ -n "$foo" ] [ -z "$foo" ] [ -n "$( -   "$file")" ] # [[          ,      : [[ -n $foo ]] [[ -z $foo ]]
      
      





37. [[ -e "$broken_symlink" ]] 1 $broken_symlink



Test symlink', , symlink , — , , , — test –e



1, symlink. ( ), :



 # bash/ksh/zsh [[ -e "$broken_symlink" || -L "$broken_symlink" ]] # POSIX sh+test [ -e "$broken_symlink" ] || [ -L "$broken_symlink" ]
      
      





38. ed file <<<«g/d\{0,3\}/s//e/g»



, ed 0 \{0,3\}. , :



 ed file <<<"g/d\{1,3\}/s//e/g"
      
      





, POSIX-, BRE ( , ed), 0 (. 5) .



39. (sub-string) expr «match»







 word=abcde expr "$word" : ".\(.*\)" bcde
      
      





«match»



 word=match expr "$word" : ".\(.*\)"
      
      





, «match» — . GNU '+'



 word=match expr + "$word" : ".\(.*\)" atch
      
      





expr



. , , ( Parameter Expansion ). , ? POSIX- (Substring Expansion):



 $ word=match $ echo "${word#?}" # PE atch $ echo "${word:1}" # SE atch
      
      





, expr



, Solaris POSIX /bin/sh



. , , . , , , .



40. UTF-8 (Byte-Order Marks, BOM)



: Unix UTF-8 BOM. , MIME- . BOM UTF-8, , ( - ) , , , . , BOM, , , MS-DOS.



: , UTF-8 8- , BOM , , ASCII-, "#!" Unix-».



http://unicode.org/faq/utf_bom.html#bom5



41. content=$(<file)



, , ( : `...`



, $(...)



, $(<file)



, `<file`



${ ...; }



(ksh)) . , , , , , , . , : .



 absolute_dir_path_x=$(readlink -fn -- "$dir_path"; printf x) absolute_dir_path=${absolute_dir_path_x%x}
      
      





, : read



.



 # Ksh (or bash 4.2+ with lastpipe enabled) readlink -fn -- "$dir_path" | IFS= read -rd '' absolute_dir_path
      
      





, read



false, NUL-, . — PIPESTATUS



. NUL-, read



true, pipefail



.



 set -o pipefail { readlink -fn -- "$dir_path"; printf '\0x'; } | IFS= read -rd '' absolute_dir_path
      
      





: Bash pipefail



PIPESTATUS



, ksh93 — pipefail



, mksh pipefail



, — PIPESTATUS



. , ksh93, read



NUL-.



42. for file in ./*; do if [[ $file != *.* ]]



, (. 3). ./



.



*.*



, ./filename



. . , (, , ), : [[ $file != ./*.* ]]



, .



 # Bash shopt -s nullglob for path in ./*; do [[ ${path##*/} != *.* ]] && rm "$path" done #    for file in *; do [[ $file != *.* ]] && rm "./${file}" done #    for file in *.*; do rm "./${file}" done
      
      





--



( 3).



 shopt -s nullglob for file in *; do [[ $file != *.* ]] && rm -- "$file" done
      
      





43. somecmd 2>&1 >>logfile



, , , , stdout stderr. , stderr . , , . , . : « , (tty), -». . tty. :



 somecmd >>logfile 2>&1
      
      





, Copy BashGuide — redirection .



44. cmd; ((! $? )) || die



$?



, . , ( ), , :



 if cmd; then ... fi
      
      





:



 cmd status=$? case $status in 0) echo success >&2 ;; 1) echo 'Must supply a parameter, exiting.' >&2 exit 1 ;; *) echo "Unknown error $status, exiting." >&2 exit "$status" esac
      
      





45. y=$(( array[$x] ))



POSIX (arithmetic expansion) ( ), (array subscript) . , , .



 $ x='$(date >&2)' #    ,  ,   $ y=$((array[$x])) #      Mon Jun 2 10:49:08 EDT 2014
      
      





:



 $ y=$((array["$x"])) Mon Jun 2 10:51:03 EDT 2014
      
      





:



 # 1.  $x,       . $ y=$((array[\$x])) # 2.    ${array[$x]}. $ y=$((${array[$x]}))
      
      





46. read num; echo $((num+1))



(. BashFAQ/054 ), num , .



 $ echo 'a[$(echo injection >&2)]' | bash -c 'read num; echo $((num+1))' injection 1
      
      





47. IFS=, read -ra fields <<< "$csv_line"



, POSIX IFS (field terminator), . , , :



 $ IFS=, read -ra fields <<< "a,b," $ declare -p fields declare -a fields='([0]="a" [1]="b")'
      
      





? (« »). Bash, . :



 $ IFS=, read -ra fields <<< "a,b,c" $ declare -p fields declare -a fields='([0]="a" [1]="b" [2]="c")'
      
      





? , IFS- . , IFS- «» , . , IFS- , , .



 $ input="a,b," $ IFS=, read -ra fields <<< "$input," $ declare -p fields declare -a fields='([0]="a" [1]="b" [2]="")'
      
      





48. export CDPATH=.:~/myProject



CDPATH. CDPATH .bashrc , , Bash- sh-, cd



, . . , :



 cd some/dir || exit cmd to be run in some/dir
      
      





./some/dir



~/myProject/some/dir



, , . cd



, , , .



cd



, :



 output=$(cd some/dir && some command)
      
      





CDPATH , cd



stdout /home/user/some/dir



, , CDPATH, some command



.



CDPATH, , ./



. unset CDPATH



, , , CDPATH.



All Articles