利用可能なマニュアルのほとんどは、書く方法に専念しています。 書く方法は必要ありません:-)
このテキストは、2008年12月13日現在のBashの落とし穴wikiの無料翻訳です。 ソースのウィキの性質により、この翻訳はオリジナルと異なる場合があります。 テキストのボリュームが大きすぎて全体を公開できないため、部分的に公開されます。
1. `ls * .mp3`のfor i
bashスクリプトで最も一般的なエラーの1つは、次のようなループです。
for i in `ls * .mp3`; #Falseを行う! いくつかのコマンド$ i#False! やった
いずれかのファイルの名前にスペースがある場合、これは機能しません。
ls *.mp3
を置換した結果は、単語が壊れています。 現在のディレクトリにファイル
01 - Don't Eat the Yellow Snow.mp3
があるとします
01 - Don't Eat the Yellow Snow.mp3
。
for
ループはファイル名の各単語を処理し、
$i
は値
"01"
、
"-"
、
"Don't"
、
"Eat"
、
"the"
、
"Yellow"
、
"Snow.mp3"
ます。
コマンド全体を引用することもできません:
for i in "` ls * .mp3` "; #Falseを行う! ...
出力全体が1つの単語と見なされるようになり、リスト内の各ファイルを通過する代わりに、サイクルは1回だけ実行されますが、
i
は値を取得します。これは、スペースを介したすべてのファイル名の連結です。
実際、
ls
使用
ls
完全に冗長です。これは、この場合は単に必要のない外部コマンドです。 それではどうですか? など:
for i in * .mp3; #はるかに良くなりますが、... いくつかのコマンド "$ i"#... catch#2を参照 やった
bashでファイル名を置き換えます。 このような置換は、文字列を単語に分割することにはなりません。
*.mp3
パターンに一致する各ファイル名は1つの単語と見なされ、各ファイル名を1回循環します。
詳細については、Bash FAQの段落20を参照してください 。
注意深い読者は、上記の例の2行目に引用符があることに気付くはずです。 これにより、スムーズにキャッチ番号2に到達します。
2. cp $ file $ターゲット
このチームの何が問題になっていますか? 変数
$file
と
$target
スペースやワイルドカードが含まれていないことを絶対に知っている場合、特別なことはないように思えます。
しかし、どんな種類のファイルに出会うかわからない場合、またはあなたが偏執的である場合、または単に良いbashプログラミングスタイルに従うことを試みている場合は、単語に入れないように変数の名前を引用符で囲みます。
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
または
$target
の値にワイルドカードテンプレートで使用される文字*、?、[..]、または(..)が含まれている場合、パターンに一致するファイルがある場合、変数の値が変換されますこれらのファイルの名前に。
"$file"
がハイフンで始まっていない限り、二重引用符でこの問題を解決します。この場合、
cp
は別のコマンドラインオプションを指定しようとしていると考えます。
回避策の1つは、
cp
コマンドとその引数の間に二重ハイフン(
--
)を挿入することです。 二重ハイフンは、
cp
にオプションの検索を停止するように指示します。
cp-"$ファイル" "$ターゲット"
ただし、このようなトリックが機能しないシステムの1つに遭遇する可能性があります。 または、実行しようとしているコマンドが
--
オプションをサポートしていません。 この場合、読み進めてください。
別の方法は、ファイル名が常にディレクトリ名で始まるようにすることです(現在のディレクトリ名の
./
を含む)。 例:
for i in ./*.mp3; する cp "$ i" /ターゲット ...
名前が「-」で始まるファイルがある場合でも、テンプレート置換メカニズムにより、変数に
./-foo.mp3
などが含まれることが保証され
cp
。これは
cp
使用しても絶対に安全です。
3. [$ foo = "bar"]
この例では、引用符は正しく配置されていません。bashでは、文字列リテラルを引用符で囲む必要はありません。 ただし、スペースやワイルドカードが含まれていないことが確かでない場合は、必ず変数を引用する必要があります。
このコードは、次の2つの理由で誤っています。
1.条件
[
使用される変数が存在しないか空の場合、行
[$ foo = "bar"]
として認識されます
[= "バー"]
「単項演算子」エラーが発生します。 (演算子「=」は単項ではなくバイナリであるため、コマンド
[
はこの構文によってショックを受けます)
2.変数にスペースが含まれている場合、変数は
[
で処理される前に異なる単語に分割されます。
[ここに複数の単語=「バー」]
これが正常であると個人的に考えても、そのような構文は誤りです。
次のように修正されます。
["$ foo" = bar]#近い!
ただし、$ fooが
-
始まる場合、このオプションは機能しません。
bashでは、この問題を解決するために、古い
test
コマンド(
[
も呼ばれる)を含めて大幅に拡張するキーワード
[[
使用できます。
[[$ foo = bar]]#正しい!
[[
and
]]
内では、変数が単語に分割されなくなり、空の変数も正しく処理されるため、変数の名前を引用符で囲む必要がなくなりました。 一方、もう一度引用符で囲んだとしても、何も害はありません。
次のようなコードを見たことがあるかもしれません。
[x "$ foo" = xbar]#正しい!
コードにはハック
x"$foo"
が必要です。これは、
[[
サポートしないシェルで動作するはずです。
$foo
が
-
で始まる場合、コマンド
[
は混乱します。
式の一部の1つが定数である場合、これを行うことができます。
[bar = "$ foo"]#とても正しい!
コマンド
[
は、「=」記号の右側の式が
-
始まることを気にしません。 彼女はこの式を文字列として使用しています。 そのような細心の注意が必要なのは左側だけです。
4. cd `dirname" $ f "`
これまでのところ、基本的に同じことについて話している。 変数の値の展開と同様に、コマンド置換の結果は、単語とファイル名の展開(パス名展開)に分割されます。 したがって、コマンドを引用符で囲む必要があります。
cd "` dirname "$ f" `"
ここで完全に明らかではないのは、引用符のシーケンスです。 Cプログラマーは、1番目と2番目の引用符と3番目と4番目の引用符をグループ化することを提案する場合があります。 ただし、この場合はそうではありません。 Bashは、コマンド内の二重引用符を最初のペアとして扱い、外側の引用符を2番目のペアとして扱います。
つまり、パーサーは逆引用符(
`
)をネストのレベルとして扱い、その内部の引用符は外側の引用符から分離されます。
同じ効果は、より好ましい
$()
構文を使用して達成できます。
cd "$(dirname" $ f ")"
$()
内の引用符はグループ化されています。
5. ["$ foo" = bar && "$ bar" = foo]
「古い」
test
コマンドまたはそれに相当する
[
中で
&&
を使用することはできません。 bashパーサーは、括弧の外側で
&&
認識し、コマンドを
&&
前後に2つに分割します。 次のオプションのいずれかを使用してください。
[bar = "$ foo" -a foo = "$ bar"]#正しい! [bar = "$ foo"] && [foo = "$ bar"]#そうです! [[$ foo = bar && $ bar = foo]]#正しい!
前の段落で説明した理由により、定数と変数を
[
-内で交換したことに注意してください。
同じことが
||
も当てはまります 。
[[
、または
-o
、または2つのコマンド
[
ます。
6. [[$ foo> 7]]
[[ ]]
内で
>
演算子が使用されている場合、数字ではなく文字列を比較するための演算子と見なされます。 場合によっては、これが機能する場合と機能しない場合があります(これは、予想以上の場合に発生します)。
>
が
[ ]
内にある場合、さらに悪いことです。この場合、指定された番号でファイル記述子からの出力をリダイレクトしています。
7
という名前の空のファイルが現在のディレクトリに表示され、
$foo
変数が空でない限り、
test
コマンドは成功します。
したがって、operator>および<は、
[ .. ]
または
[[ .. ]]
内の数値の比較には使用できません。
2つの数値を比較する場合は、
(( ))
使用します。
((foo> 7))#そうです!
BashではなくBourne Shell(sh)向けに記述している場合、正しい方法は次のとおりです。
[$ foo -gt 7]#そうです!
test ... -gt ...
は、引数の少なくとも1つが整数でない場合にエラーを
test ... -gt ...
ことに注意してください。 したがって、引用符が正しく配置されているかどうかは問題ではありません。変数が空であるか、スペースが含まれているか、値が整数でない場合、いずれの場合でもエラーが発生します。
test
コマンドで変数を使用する前に、変数の値を注意深く確認してください。
二重角括弧もこの構文をサポートしています。
[[$ foo -gt 7]]#そうです!
7.カウント= 0; grep foo bar | 行を読みながら; do((count ++)); 完了; echo "行数:$カウント"
一見、このコードは問題ないように見えます。 しかし、実際には、ループを終了した後も
$count
変数は変更されないままであるため、bash開発者は驚いたことになります。 なぜこれが起こっているのですか?
パイプラインの各コマンドは個別のサブシェルで実行され、サブシェル内の変数の変更は、親シェルインスタンス(つまり、このコードを呼び出したスクリプト)のこの変数の値に影響しません。
この場合、
for
ループはパイプラインの一部であり、変数
$count
コピーを持つ別のサブシェルで実行され、親シェルの変数
$count
値で初期化されます: "0"。 ループが終了すると、ループで使用された
$count
のコピーは破棄され、
echo
コマンドは変更されていない初期値の
$count
( "0")を表示します。
これにはいくつかの方法があります。
サブシェルでループを実行できます(少し曲がっていますが、より簡単で理解しやすく、shで動作します)。
#POSIX互換 カウント= 0 猫/ etc / passwd | ( 行を読みながら; する カウント= $((カウント+ 1)) やった echo "行の総数:$ count" )
サブシェルの作成を完全に回避するには、リダイレクトを使用します(Bourne shell(sh)では、リダイレクト用のサブシェルも作成されるため、このトリックはbashでのみ機能します)。
#bashのみ! カウント= 0 行を読みながら; する カウント= $(($カウント+ 1)) </ etc / passwd echo "行の総数:$ count"
前の方法はファイルに対してのみ機能しますが、コマンドラインの出力を1行ずつ処理する必要がある場合はどうなりますか? プロセス置換を使用する:
LINEを読みながら; する echo "-> $ LINE" done <<(grep PATH / etc / profile)
サブシェルの問題を解決するためのさらに興味深い方法は、 Bash FAQ#24で説明されています。
8. if [grep foo myfile]
多くの人は
if
後に角括弧を置く習慣に戸惑い、初心者はしばしば、条件付きC言語構造の括弧のように、条件付き構文の一部であるという誤った印象を得る。
しかし、そのような意見は間違いです! 開始角括弧(
[
)は構文の一部ではありませんが、コマンドの終了引数が最後の引数でなければならないことを除いて、
test
コマンドと同等のコマンドです。
構文の
if
:
コマンドの場合 それから コマンド elifコマンド#オプション それから コマンド else#オプション コマンド fi
ご覧のとおり、
[
または
[[
はありません!
繰り返しますが、
[
は引数を取り、戻りコードを返すコマンドです。 すべての通常のコマンドと同様に、エラーメッセージを表示できますが、原則として、STDOUTには何も生成しません。
コマンドの最初のセットを実行し、このセットの最後のコマンドのリターンコードに応じて、「then」セクションのコマンドブロックを実行するか、スクリプトの実行を継続するかを決定します。
grep
出力に応じて決定する必要がある場合は、括弧、角括弧、中括弧、バックティック、またはその他の構文要素で囲む必要はありません。
if
後にコマンドとして
grep
を書くだけ
if
:
if grep foo myfile> / dev / null; それから ... fi
標準の
grep
出力を破棄することに注意してください。検索結果は必要ありません。ファイルに行が存在するかどうかを知りたいだけです。
grep
が文字列を見つけると、0を返し、条件が満たされます。 それ以外の場合(ファイルに行がありません)、
grep
は0以外の値を返します。GNUgrepでは、リダイレクト
>/dev/null
を
-q
オプションに置き換えることができます。
9. if [bar = "$ foo"]
前の段落で説明したように、
[
はコマンドです。 他のコマンドと同様に、bashはコマンドの後にスペースが続き、最初の引数が続き、再びスペースが続くと想定します。 したがって、スペースなしですべてを書くことはできません! それはまさにこのようなものです:
if [bar = "$ foo"]
bar
、
=
、
"$foo"
(置換後、ただし単語分割なし)および
]
は
[
コマンドの引数であるため、シェルが引数の開始位置と終了位置を判別できるように、引数の各ペアの間にスペースが必要です。
10. if [[a = b] && [c = d]]
再び同じ間違い。
[
コマンドであり、
if
と条件の間の構文要素ではなく、さらにグループ化の手段でもありません。 C構文を使用して、括弧を単に正方形に置き換えるだけではbash構文に変換できません。
難しい条件を実装したい場合、正しい方法は次のとおりです。
if [a = b] && [c = d]
ここでは、&&演算子によって結合されたifの後に2つのコマンドがあることに注意してください。 このコードは、次のようなコマンドと同等です。
テストa = b &&テストc = dの場合
最初のテストコマンドが
false
(ゼロ以外の数値)を返す場合、条件の本文はスキップされます。
true
返す
true
、2番目の条件は
true
です。
true
返す
true
、条件本体が実行されます。
継続する。
この翻訳の最初の公開は私のブログのページで行われました 。