web-dev-qa-db-ja.com

要素にスペースがあるbash配列

私は私のカメラからファイル名のbashで配列を構築しようとしています:

FILES=(2011-09-04 21.43.02.jpg
2011-09-05 10.23.14.jpg
2011-09-09 12.31.16.jpg
2011-09-11 08.43.12.jpg)

ご覧のとおり、各ファイル名の中央にスペースがあります。

各名前を引用符で囲み、スペースをバックスラッシュでエスケープしてみましたが、どちらも機能しません。

配列要素にアクセスしようとすると、スペースが引き続き要素区切り文字として扱われます。

名前の中にスペースがあるファイル名を適切にキャプチャするにはどうすればよいですか?

128
abelenky

問題の一部は、要素へのアクセス方法にあると思います。簡単なfor elem in $FILESを実行すると、同じ問題が発生します。ただし、そのようにインデックスを使用して配列にアクセスする場合、数値またはエスケープを使用して要素を追加すると機能します:

for ((i = 0; i < ${#FILES[@]}; i++))
do
    echo "${FILES[$i]}"
done

$FILESのこれらの宣言はすべて機能するはずです。

FILES=(2011-09-04\ 21.43.02.jpg
2011-09-05\ 10.23.14.jpg
2011-09-09\ 12.31.16.jpg
2011-09-11\ 08.43.12.jpg)

または

FILES=("2011-09-04 21.43.02.jpg"
"2011-09-05 10.23.14.jpg"
"2011-09-09 12.31.16.jpg"
"2011-09-11 08.43.12.jpg")

または

FILES[0]="2011-09-04 21.43.02.jpg"
FILES[1]="2011-09-05 10.23.14.jpg"
FILES[2]="2011-09-09 12.31.16.jpg"
FILES[3]="2011-09-11 08.43.12.jpg"
105
Dan Fego

配列のアイテムにアクセスする方法に何か問題があるはずです。方法は次のとおりです。

for elem in "${files[@]}"
...

bash manpage から:

配列の任意の要素は、$ {name [subscript]}を使用して参照できます。 ...添え字が@または*の場合、Wordはnameのすべてのメンバーに展開されます。これらの添え字は、Wordが二重引用符で囲まれている場合にのみ異なります。 Wordが二重引用符で囲まれている場合、$ {name [*]}は、各配列メンバーの値が最初の文字で区切られた単一のWordに展開されますIFS特殊変数の$ {name [@]}は、nameの各要素を個別のWordに展開します。

もちろん、単一のメンバーにアクセスするときは二重引用符も使用する必要があります

cp "${files[0]}" /tmp
83

要素の区切り文字としてスペースを停止するには、IFSを使用する必要があります。

FILES=("2011-09-04 21.43.02.jpg"
       "2011-09-05 10.23.14.jpg"
       "2011-09-09 12.31.16.jpg"
       "2011-09-11 08.43.12.jpg")
IFS=""
for jpg in ${FILES[*]}
do
    echo "${jpg}"
done

に基づいて分離する場合。次に、IFS = "。"を実行します。それがあなたを助けることを願っています:)

37
Khushneet

問題の要素にどのようにアクセスしているかが他の人に同意します。配列の割り当てでファイル名を引用するのは正しいです:

FILES=(
  "2011-09-04 21.43.02.jpg"
  "2011-09-05 10.23.14.jpg"
  "2011-09-09 12.31.16.jpg"
  "2011-09-11 08.43.12.jpg"
)

for f in "${FILES[@]}"
do
  echo "$f"
done

"${FILES[@]}"という形式の配列を二重引用符で囲むと、配列要素ごとに1つのWordに配列が分割されます。それ以上の単語分割は行いません。

"${FILES[*]}"の使用にも特別な意味がありますが、結合 $ IFSの最初の文字を持つ配列要素であるため、one Wordになります。あなたが欲しい。

裸の${array[@]}または${array[*]}を使用すると、その展開の結果がさらにワード分割されます。そのため、スペースではなくワード(および$IFSの他のもの)で分割されます。配列要素ごとに1ワード。

Cスタイルのforループを使用しても問題はありません。明確でない場合でも、Wordの分割について心配する必要はありません。

for (( i = 0; i < ${#FILES[@]}; i++ ))
do
  echo "${FILES[$i]}"
done
11
Dean Hall

元の質問の引用/エスケープの問題に対する正確な答えではありませんが、おそらく実際にはopにとってより有用なものです:

unset FILES
for f in 2011-*.jpg; do FILES+=("$f"); done
echo "${FILES[@]}"

もちろん、特定の要件に合わせて式を採用する必要がある場合(例:すべての場合は*.jpg、特定の日の写真のみの場合は2001-09-11*.jpg)。

2
TNT

エスケープは機能します。

#!/bin/bash

FILES=(2011-09-04\ 21.43.02.jpg
2011-09-05\ 10.23.14.jpg
2011-09-09\ 12.31.16.jpg
2011-09-11\ 08.43.12.jpg)

echo ${FILES[0]}
echo ${FILES[1]}
echo ${FILES[2]}
echo ${FILES[3]}

出力:

$ ./test.sh
2011-09-04 21.43.02.jpg
2011-09-05 10.23.14.jpg
2011-09-09 12.31.16.jpg
2011-09-11 08.43.12.jpg

文字列を引用しても、同じ出力が生成されます。

2
Chris Seymour

次のような配列がある場合:#!/ bin/bash

Unix[0]='Debian'
Unix[1]="Red Hat"
Unix[2]='Ubuntu'
Unix[3]='Suse'

for i in $(echo ${Unix[@]});
    do echo $i;
done

あなたは得るでしょう:

Debian
Red
Hat
Ubuntu
Suse

理由はわかりませんが、ループはスペースを分解し、個々のアイテムとして配置します。引用符で囲んでもです。

これを回避するには、配列内の要素を呼び出す代わりに、インデックスを呼び出します。インデックスは、引用符で囲まれた完全な文字列を取ります。引用符で囲む必要があります!

#!/bin/bash

Unix[0]='Debian'
Unix[1]='Red Hat'
Unix[2]='Ubuntu'
Unix[3]='Suse'

for i in $(echo ${!Unix[@]});
    do echo ${Unix[$i]};
done

次に、取得します:

Debian
Red Hat
Ubuntu
Suse
1
Jonni2016aa

bashの使用にとどまっていない場合、ファイル名のスペースの異なる処理は fish Shell の利点の1つです。 「a b.txt」と「b c.txt」の2つのファイルを含むディレクトリを考えます。 bashを使用して別のコマンドから生成されたファイルのリストを処理する合理的な推測を次に示しますが、ファイル名にスペースが含まれているため失敗します。

# bash
$ for f in $(ls *.txt); { echo $f; }
a
b.txt
b
c.txt

fishを使用すると、構文はほぼ同じですが、結果は期待どおりです。

# fish
for f in (ls *.txt); echo $f; end
a b.txt
b c.txt

Fishはコマンドの出力をスペースではなく改行で分割するため、動作が異なります。

改行ではなくスペースで分割したい場合は、fishに非常に読みやすい構文があります。

for f in (ls *.txt | string split " "); echo $f; end
0
Mark Stosberg

別の解決策は、「for」ループの代わりに「while」ループを使用することです。

index=0
while [ ${index} -lt ${#Array[@]} ]
  do
     echo ${Array[${index}]}
     index=$(( $index + 1 ))
  done
0
Javier Salas