コマンドの出力をシェルの変数に読み込み、改行だけでなく空白ごとにテキストを分割します。

コマンドの出力をシェルの変数に読み込み、改行だけでなく空白ごとにテキストを分割します。

私は FreeBSD 11 で sh (bash/csh ではない) を使用していますが、これが理解できません:

コンソール内

指示:zpool status -v

結果:

  pool: My_pool
 state: ONLINE
status: One or more devices is currently being resilvered.  The pool will
        continue to function, possibly in a degraded state.
action: Wait for the resilver to complete.

スクリプトで行ごとに分割し、一度に 1 行ずつ印刷する:

#!/bin/sh
zp="$(zpool status -v)"
for line in $zp; do
  echo "$line%"
done

結果:

pool:%
My_pool%
state:%
ONLINE%
status:%
One%
or%
more%
devices%
is%
currently%
being%
resilvered.%
The%
pool%
will%
continue%
to%
function,%
possibly%
in%
a%
degraded%
state.%
action:%
Wait%

私が見つけたものによると、私が使用している構文は sh に対して正しく、単語ごとではなく行ごとに読み取るはずです。

何が足りないのでしょうか?

答え1

あなたが見つけた内容に困惑しています。使用した構文は sh には正しく、改行だけでなく空白でも分割されます。これは広く文書化されています。改行で分割されると考えるのはよくある誤解ではありません。(分割されることを理解していないのはよくある誤解です。)

より正確には、 のように変数展開を引用符で囲まないことは$zp、「split+glob」演算子と呼ばれることもあります。これは次のことを行います。

  1. 変数の値を取得します。これは文字列です。
  2. この値をフィールド区切り文字ごとに分割します。フィールド区切り文字は変数の値に含まれる文字ですIFS。デフォルトでは、この変数にはスペース、タブ、改行(変数が設定されていない場合にもこのデフォルトが使用されます。)結果は文字列のリストになります。
  3. リストの各要素をワイルドカード (glob) パターンとして扱います。1 つ以上のファイル名に一致する場合、その要素は一致するファイル名のリストに置き換えられます。それ以外の場合、要素はそのまま残されます。

たとえば、ディレクトリにfoo、、barおよびというファイルが含まれている場合baz、次のスクリプトは、、およびの 3 行を出力a*barますbaz

zq='a* b*'
for word in $zq; do echo "$word"; done

IFS文字列を改行で分割したい場合は、を改行に設定し、ワイルドカードの展開を無効にすることで分割できます。

#!/bin/sh
set -f
IFS='
'
zq=…
for line in $zq; do
done

これはすべての sh および csh バリアントに適用されます。

参照シェル スクリプトが空白文字やその他の特殊文字で動作しなくなるのはなぜですか?

答え2

行を読み込むには、シェルの組み込みの読む関数、

$ヘルプを読む | ヘッド -2

読み取り: 読み取り [-ers] [-u fd] [-t タイムアウト] [-p プロンプト] [-a 配列] [-n nchars] [-d 区切り文字] [名前...]

標準入力から1行読み込まれるか、-uオプションが指定されている場合はファイル記述子FDから読み込まれる。

これをループ内に入れるwhileと、次の行が作成されます。

$ wc -l -w ~/.profile 
  24      47 /Users/jklowden/.profile
$ for W in $(cat ~/.profile); do echo $W; done | wc -l 
  47
$ while read L; do echo $L; done < ~/.profile | wc -l 
  24

関連情報