web-dev-qa-db-ja.com

bashの文字列変数の行を繰り返す

コマンドlsblkを使用してUSBデバイスを一覧表示するスクリプトがあります。

コマンド:

$ lsblk -o NAME,TRAN,VENDOR,MODEL | grep usb

その結果

sdb   usb    Kingston DataTraveler 2.0
sdc   usb    Kingston DT 101 G2 

後で動作するように結果を変数に保存したいので、

$ usbs=$(lsblk -o NAME,TRAN,VENDOR,MODEL | grep usb)

私が期待していたのは、変数usbsが上記のように2つの行全体に結果を格納することです。しかし、実行すると:

for i in ${usbs[@]}; do
  echo $i
done

結果を単語に分割します:

sdb
usb
Kingston
DataTraveler
2.0
sdc
usb
Kingston
DT
101
G2

質問:grepコマンドを使用して、コマンドの結果を2つの行全体として保存する方法はありますか?

結果をファイルにダンプしてから読み取るのではなく、簡単な解決策があるかどうかを知りたいです。

5

質問:grepコマンドを使用して、コマンドの結果を2つの行全体として保存する方法はありますか?

はい、あなたの割り当てコードは正しかったです:

usbs=$(lsblk -o NAME,TRAN,VENDOR,MODEL | grep usb)

これは正確に必要なことを行います。 (配列ではなく)単一の変数に、改行で区切られたlsblkからの2行を格納します。しかし、forループは、その変数を読み取るための適切なツールではありません。 whileループの方がはるかに優れています。一部のリーダーにはUSBデバイスが接続されていない可能性があるため、データの例を以下に示します。

t=$(echo foo bar; echo baz bing;)
while read i ; do echo "$i" ; done <<< "$t"

出力:

foo bar
baz bing

これはさらに短い方法です:

xargs -L 1 <<< "$t"

注:プレーン[〜#〜] posix [〜#〜]スタイル変数xは配列ではありませんが、bashxを配列表記を使用して識別できるため、文句を言うことはありませんxが配列でないことについて。デモ:

x=f
echo $x ${x[0]} ${x[@]}

出力:

f f f

ただし、xは配列ではありません。あった場合、このコードは、(bashパラメータ変換を使用してAssignment演算子)、間違いなく配列を出力します:

echo "${x[@]@A}"

...それはしません:

x='f'

対照的に、上記をが配列である場合の外観と比較してみましょう。最初に非常によく似た配列yを作成し、次にAssignment演算子を使用して違いを示します。

y=(f)
echo "${y[@]@A}"

出力:

declare -a y=([0]="f")
3
agc

これは readarray/mapfile を使用するのに適した状況です。

readarray -t usbs < <(lsblk -o NAME,TRAN,VENDOR,MODEL | grep usb)

これにより、各行が独自の要素に区切られた出力を持つ配列が作成されます。

あなたの場合、それは次のような配列になります:

usbs=(
'sdb   usb    Kingston DataTraveler 2.0'
'sdc   usb    Kingston DT 101 G2'
)

同様に、出力全体を単一の変数(配列ではない)に割り当てます。これは基本的にこれを行います。

usbs='sdb   usb    Kingston DataTraveler 2.0
sdc   usb    Kingston DT 101 G2 '

それを配列にするためには、次のようにします:

usbs=($(lsblk -o NAME,TRAN,VENDOR,MODEL | grep usb))

しかし、これは空白で区切られた各単語を独自の要素に分割します。

usbs=(
sdb
usb
Kingston
DataTraveler
2.0
sdc
usb
Kingston
DT
101
G2
)

複数のコメンテーターによって指摘されており、通常は常にベストプラクティスであるように、変数は引用符で囲む必要があります。この場合、通常は常に変数を引用する必要があります。

まず、それは問題に対処する正しい方法ではないと思います。 「人を殺してはいけないので、刑務所に行くから」と言ったようなものです。

同様に、変数を引用符で囲まないでください。そうしないと、セキュリティの脆弱性が発生します。変数を引用しないのは間違っているためです(しかし、刑務所への恐怖が助けになるなら、なぜそうしないのか)。

  • ステファンシャゼラス

for i in ${usbs[@]}; doの場合、iusbsfor i in "${usbs[@]}"; doのように引用すると、iusbsのすべてのelementに設定されます。望まれるように。

10
jesse_b

これは主に 改行とbash変数 の複製ですが、配列は対象外です。そこから、複数行を含む変数を使用するには、パラメーター展開を改行で分割し、グロビングをスキップする必要があります。データによっては、他のマングリングを回避できる場合があります。

 usbs=$( lsusb ... )
 IFS=$'\n'  # ksh bash zsh; in other shells you may need to quote an actual newline
 set -o noglob  # or more tersely set -f
 for i in $usbs; do
   printf '%s\n' "$i" # not echo which sometimes modifies some data
 done
 # if you do further things in the same script (or function) you may 
 # need to re-set IFS and/or glob, which may require saving them first

配列の場合、readarray/mapfile Jesse_bによって提案されているように、行で分割されているため、最も単純です。しかし、あなたはできる上記のように「手動」でそれを行う:

set -o noglob  # ditto 
IFS=$'\n' usbs=( $( lsusb ... ) )  # only ksh up has arrays so $'' safe
# set +o noglob or set +f if needed
for i in "${usbs[@]}"; do # quoted array[@] forces splits equal to array elements only
  printf '%s\n' "$i"
done
4