web-dev-qa-db-ja.com

変数を代入しただけですが、echo $ variableが他のものを表示しています

これは、echo $varが、割り当てられたものとは異なる値を示すことがある一連のケースです。これは、割り当てられた値が "二重引用符"、 "一重引用符"、または引用符で囲まれていないかに関係なく発生します。

シェルに変数を正しく設定させるにはどうすればよいですか。

アスタリスク

期待される出力は/* Foobar is free software */ですが、代わりにファイル名のリストを取得します。

$ var="/* Foobar is free software */"
$ echo $var 
/bin /boot /dev /etc /home /initrd.img /lib /lib64 /media /mnt /opt /proc ...

角かっこ

期待値は[a-z]です、しかし時々私は代わりに一文字を得ます!

$ var=[a-z]
$ echo $var
c

改行(改行)

期待値は別々の行のリストですが、代わりにすべての値が1行になっています。

$ cat file
foo
bar
baz

$ var=$(cat file)
$ echo $var
foo bar baz

複数のスペース

私は慎重に整列されたテーブルヘッダを期待していましたが、その代わりに複数のスペースが消えるか1つに折りたたまれるかのどちらかです!

$ var="       title     |    count"
$ echo $var
title | count

タブ

2つのタブ区切り値が必要ですが、代わりに2つのスペース区切り値が使用されます。

$ var=$'key\tvalue'
$ echo $var
key value
75
that other guy

上記のすべての場合で、変数は正しく設定されていますが、正しく読み取られていません。正しい方法は、二重引用符を使用することです参照時

echo "$var"

これは与えられたすべての例で期待値を与えます。常に変数参照を引用してください。


どうして?

変数が引用符で囲まれていない場合、次のようになります。

  1. Undergo field split ここで、値は空白で複数の単語に分割されます(デフォルト)。

    前:/* Foobar is free software */

    変更後:/*Foobarisfreesoftware*/

  2. これらの各単語は パス名展開 を受けます。ここで、パターンは一致するファイルに展開されます。

    前:/*

    後:/bin/boot/dev/etc/home、...

  3. 最後に、すべての引数はechoに渡されます。これは 単一のスペースで区切られた を書き出します。

    /bin /boot /dev /etc /home Foobar is free software Desktop/ Downloads/
    

    変数の値の代わりに。

変数がクォートされている場合、次のようになります。

  1. その値を代用してください。
  2. ステップ2はありません。

Wordの分割とパス名の展開が特に必要でない限り、常にすべての変数参照を引用符で囲む必要があるのはこのためです。 shellcheck のようなツールは役に立ちますが、上記のすべての場合に引用符がないことを警告します。

94
that other guy

なぜこれが起こっているのか知りたいと思うかもしれません。 あの他の人による素晴らしい説明 と一緒に、 なぜ私のシェルスクリプトは空白や他の特殊文字を詰まらせるのですか?Gilles によって書かれました。 = in nixおよびLinux

なぜ"$foo"を書く必要があるのですか?引用符なしではどうなりますか?

$fooは、「変数fooの値を取る」という意味ではありません。もっと複雑なことを意味します。

  • まず、変数の値を取ります。
  • フィールド分割:その値を空白で区切られたフィールドのリストとして扱い、結果のリストを作成します。たとえば、変数にfoo * bar ​が含まれている場合、このステップの結果は3要素リストfoo*barになります。
  • ファイル名の生成:各フィールドをグロブ、つまりワイルドカードパターンとして扱い、それをこのパターンに一致するファイル名のリストで置き換えます。パターンがどのファイルとも一致しない場合は、変更されません。この例では、これにより、現在のディレクトリ内のファイルのリストが続くfoo、そして最後にbarを含むリストが表示されます。現在のディレクトリが空の場合、結果はfoo*barです。

結果は文字列のリストです。シェル構文には、リストコンテキストと文字列コンテキストの2つのコンテキストがあります。フィールド分割とファイル名生成はリストコンテキストでのみ起こりますが、それはほとんどの場合です。二重引用符は文字列コンテキストを区切ります。二重引用符で囲まれた文字列全体は単一の文字列であり、分割されません。 (例外:位置パラメータのリストに展開するための"$@"。たとえば、位置パラメータが3つある場合、"$@""$1" "$2" "$3"と同じです。 $ *と$ @の違いは何ですか? を参照してください。)

同じことが$(foo)または`foo`によるコマンド置換にも起こります。ちなみに、`foo`は使用しないでください。その引用規則は奇妙で移植性がなく、現代のすべてのシェルは$(foo)をサポートしています。これは直感的な引用規則があること以外はまったく同じです。

算術代入の出力も同様に展開されますが、展開不可能な文字のみが含まれるため、通常は問題になりません(IFSに数字や-が含まれていないと仮定します)。

引用符を省略できる場合の詳細については、 二重引用符が必要な場合 を参照してください。

この問題がすべて発生することを意味しない限り、変数とコマンドの置換には必ず二重引用符を使用してください。気を付けてください:引用符を省くとエラーだけでなく セキュリティホール にもつながります。

12
fedorqui

正確な値を取得するには、ユーザーの二重引用符を使用してください。このような:

echo "${var}"

そしてそれはあなたの値を正しく読みます。

3
vanishedzhou

引用に失敗することによって引き起こされる他の問題に加えて、-nおよび-eは引数としてechoによって消費される可能性があります。 (echoのPOSIX仕様に準拠しているのは前者だけですが、いくつかの一般的な実装はこの仕様に違反し、同様に-eを消費します)。

これを避けるには、詳細が必要な場合はprintfの代わりにechoを使用してください

したがって:

$ vars="-e -n -a"
$ echo $vars      # breaks because -e and -n can be treated as arguments to echo
-a
$ echo "$vars"
-e -n -a

ただし、echoを使用する場合は、正しい引用符で常に保存されるとは限りません。

$ vars="-n"
$ echo $vars
$ ## not even an empty line was printed

...これに対して意志printfであなたを救います:

$ vars="-n"
$ printf '%s\n' "$vars"
-n
3
Charles Duffy

echo $varの出力はIFS変数の値に大きく依存します。デフォルトでは、スペース、タブ、および改行文字が含まれています。

[ks@localhost ~]$ echo -n "$IFS" | cat -vte
 ^I$

これは、Shellがフィールド分割(またはWord分割)を行っているときに、これらの文字すべてをWordの区切り文字として使用することを意味します。これをエコーするために二重引用符なしで変数を参照すると($var)、予想される出力が変更されます。

(二重引用符を使用する以外に)Wordの分割を防ぐための1つの方法は、IFSをnullに設定することです。 http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05 を参照してください。

IFSの値がnullの場合、フィールド分割は行われません。

Nullに設定すると、空の値に設定されます。

IFS=

テスト:

[ks@localhost ~]$ echo -n "$IFS" | cat -vte
 ^I$
[ks@localhost ~]$ var=$'key\nvalue'
[ks@localhost ~]$ echo $var
key value
[ks@localhost ~]$ IFS=
[ks@localhost ~]$ echo $var
key
value
[ks@localhost ~]$ 
1
ks1322