変数を割り当てただけですが、echo $ variableは別のことを示しています


104

これは、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]、代わりに1文字が表示されることがあります。

$ 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

2
これをしてくれてありがとう。改行が頻繁に発生します。だからvar=$(cat file)大丈夫ですがecho "$var"、必要です。
2015

3
ところで、これはBashPitfalls#14でもあります:mywiki.wooledge.org/BashPitfalls#echo_.24foo
Charles Duffy


回答:


139

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

echo "$var"

これにより、与えられたすべての例で期待される値が得られます。常に変数参照を引用してください!


どうして?

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

  1. 受けフィールド分割値(デフォルトで)空白に複数の単語に分割されます。

    前: /* 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はありません。

これが、特に単語の分割とパス名の拡張を必要としない限り、常にすべての変数参照を引用する必要がある理由です。shellcheckなどのツールが役立ち、上記のすべてのケースで引用符の欠落について警告します。


常に機能しているわけではありません。例を挙げましょう。paste.ubuntu.com
p

1
うん、$(..)末尾の改行を取り除きます。var=$(cat file; printf x); var="${var%x}"それを回避するために使用できます。
他の男の

17

なぜこれが起こっているのか知りたいかもしれません。その他の人によるすばらしい説明とともに、なぜシェルスクリプトが空白やその他の特殊文字を詰まらせるのかを参照してください。GillesUnixおよびLinuxで記述したもの:

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

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

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

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

同じことが、$(foo)またはを使用したコマンド置換にも起こり `foo`ます。`foo`余談ですが、使用しないでください。その引用ルールは奇妙で移植性がなく、すべての最新のシェル$(foo)は、直感的な引用ルールを除いて完全に同等のものをサポートしています。

算術置換の出力でも同じ展開が行われますが、展開不可能な文字のみが含まれているため、通常は問題になりIFSません(数字またはが含まれていないと 想定-)。

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

この厳密な規則がすべて発生することを意味しない限り、変数とコマンドの置換の前後には常に二重引用符を使用することを忘れないでください。注意してください:引用符を省略すると、エラーだけでなくセキュリティホールにつながる可能性があり ます。


7

引用の失敗によって引き起こされる他の問題に加えて、-nそして引数として-e消費さechoれる可能性があります。(前者のみがのPOSIX仕様に従って合法ですechoが、いくつかの一般的な実装は仕様に違反しており-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

いいですね、これには適切な重複除去が必要です!これが質問のタイトルに合うことに同意しますが、ここで見るに値する可視性が得られるとは思いません。「なぜ-e/ -n/ backslashが表示されないのですか?」という新しい質問はどうですか?ここからリンクを適宜追加できます。
他の男、

もしかして消費-nだけでなくとして
Pesa19年

1
@PesaThe、いや、私が意味した-e。の標準でechoは、最初の引数がの場合の出力は指定されていないため-n、その場合、可能なすべての出力が有効になります。そのような規定はありません-e
Charles Duffy

あ...読めない。私の英語のせいにしよう。説明ありがとう。
Pesa19年

6

正確な値を取得するには、ユーザーを二重引用符で囲みます。このような:

echo "${var}"

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


作品..ありがとう
Tshilidzi Mudau

2

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

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

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

(二重引用符を使用する以外に)単語の分割を防ぐ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 ~]$ 

2
またset -f、グロビングを防止する必要があります
その他の人は

@thatotherguy、パス拡張の最初の例には本当に必要ですか?でIFSnullに設定され、echo $varに展開されるecho '/* Foobar is free software */'と、パスの拡張は、単一引用符で囲まれた文字列の内部で行われていません。
ks1322 2015年

1
はい。あなたならばmkdir "/this thing called Foobar is free software etc/"、あなたはそれがまだ拡大していることがわかります。この例の方が明らかに実用的です[a-z]
他の男

[a-z]たとえば、これは理にかなっています。
ks1322 2015年

2

ks1322から回答は、使用中に問題を特定するのに役立ちましたdocker-compose exec

-Tフラグを省略する場合は、docker-compose exec出力を中断する特殊文字を追加すると、次のb代わりに表示され1bます。

$ test=$(/usr/local/bin/docker-compose exec db bash -c "echo 1")
$ echo "${test}b"
b
echo "${test}" | cat -vte
1^M$

-Tフラグ、docker-compose exec期待通りに動作します:

$ test=$(/usr/local/bin/docker-compose exec -T db bash -c "echo 1")
$ echo "${test}b"
1b

-2

変数を引用符で囲むことに加えて、trスペースを使用して変数の出力を改行に変換することもできます。

$ echo $var | tr " " "\n"
foo
bar
baz

これは少し複雑ですが、配列変数間の区切り文字として任意の文字を置き換えることができるため、出力に多様性が追加されます。


2
しかし、これはすべてのスペースを改行に置き換えます。引用すると、既存の改行とスペースが保持されます。
user000001

はい、そうです。私はそれが変数内にあるものに依存すると思います。trテキストファイルから配列を作成するには、実際には別の方法を使用します。
Alek

3
変数を適切に引用しないことで問題を作成し、改造された余分なプロセスでそれを回避することは、良いプログラミングではありません。
tripleee 2016

@アレク、...エラー、何?trテキストファイルから配列を適切に/正しく作成する必要はありません。IFSを設定することで、必要なセパレータを指定できます。たとえばIFS=$'\n' read -r -d '' -a arrayname < <(cat file.txt && printf '\0')、bash 3.2(広く流通している最も古いバージョン)までずっと機能し、cat失敗した場合は終了ステータスをfalseに正しく設定します。あなたが望むなら、たとえば、タブの代わりに改行、あなただけ置き換えたい$'\n'$'\t'
Charles Duffy

1
あなたのような何かをやっている場合@Alek、... arrayname=( $( cat file | tr '\n' ' ' ) )のは、複数の層の上に壊れていること、次に、:それは(そう、あなたの結果をグロブだ*カレントディレクトリ内のファイルの一覧になり)、そしてそれはせずに全く同じように動作しますtr(または、catそれについてarrayname=$( $(<file) )言えば、使用するだけで同じ方法で壊れますが、非効率的ではありません)。
Charles Duffy 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.