要素にスペースを含むbash配列


150

私は私のカメラからのファイル名の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)

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

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

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

名前の中にスペースを含むファイル名を正しくキャプチャするにはどうすればよいですか?


昔ながらの方法でファイルを追加してみましたか?好きFILES[0] = ...?(編集:私はやっただけで、うまくいきません。面白いです)。
ダンフェゴ2012年


ここでのすべての答えは、Cygwinを使用することでうまく機能しません。ファイル名、ピリオドにスペースが含まれていると、奇妙なことが行われます。テキストファイルに「配列」を作成して、操作するすべての要素のリストを作成し、ファイル内の行を繰り返し処理することで、この問題を回避します。配列=(find . -maxdepth 1 -type f -iname \*.$1 -printf '%f\n'); $ {array [@]}の要素用; $ elementをエコーし​​ます。完了
アレックスホール

回答:


121

問題は、要素へのアクセス方法に一部関係していると思います。私が単純な場合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"

6
配列要素(などecho "${FILES[$i]}")を使用する場合は、二重引用符を使用する必要があることに注意してください。これは問題ではありませんechoが、それをファイル名として使用するすべての場合に影響します。
Gordon Davisson 2012年

26
で要素をループできる場合は、インデックスをループする必要はありませんfor f in "${FILES[@]}"
Mark Edgar

10
@MarkEdgar 配列のメンバーにスペースがある場合、$ {FILES [@]}のfor fで問題が発生します。スペース全体が既存のメンバーを2つ以上の要素に分割しているため、配列全体が再度解釈されるようです。「」は非常に重要なようです
マイケルショー

1
シャープ(#)記号はfor ((i = 0; i < ${#FILES[@]}; i++))ステートメントで何をしますか?
Michal Vician 2018

4
私は6年前にこれに答えましたが、配列FILESの要素数のカウントを取得することだと思います。
Dan Fego 2018

91

配列の項目にアクセスする方法に問題があるはずです。方法は次のとおりです。

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

bashのマンページから:

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

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

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

3
この束の中で最もクリーンでエレガントなソリューションですが、配列で定義されている各要素を引用する必要があることを繰り返します。
異端者

Dan Fegoの答えは効果的ですが、これは要素内のスペースを処理するためのより慣用的な方法です。
Daniel Zhang

3
他のプログラミング言語から来て、その抜粋からの用語は理解するのが本当に難しいです。さらに、構文は不可解です。あなたがもう少しそれに入ることができるならば、私は非常に感謝しますか?特にexpands to a single word with the value of each array member separated by the first character of the IFS special variable
CL22

1
はい、二重引用符がそれを解決しており、これは他のソリューションよりも優れていることに同意します。さらに説明すると、他のほとんどは二重引用符が欠けています。あなたは正しいです:for elem in "${files[@]}"、彼らは持っていますがfor elem in ${files[@]}-スペースは拡張を混乱させ、個々の単語で実行しようとします。
2017年

これは、「GNU bash、バージョン3.2.57(1)-release(x86_64-apple-darwin18)」を使用するmacOS 10.14.4では機能しません。おそらく古いバージョンのbashのバグでしょうか?
Mark Ribau

43

要素の区切り文字としてスペースを停止するには、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 = "を実行します。" それがあなたを助けることを願っています:)


3
配列を割り当てる前にIFS = ""を移動する必要がありましたが、これが正解です。
rob

複数の配列を使用して情報を解析していますが、そのうちの1つだけでIFS = ""が機能します。IFS = ""を使用すると、他のすべての配列はそれに応じて解析を停止します。これについて何かヒントはありますか?
パウロペドロソ

パウロ、あなたのケースにとってより良いかもしれない別の答えをここで見てください:stackoverflow.com/a/9089186/1041319。IFS = ""を試したことがなく、エレガントに解決されているようですが、この例は、場合によっては問題が発生する理由を示しています。IFS = ""を1行で設定することもできますが、それでも他のソリューションよりも混乱する可能性があります。
arntg 2017年

また、bashでも機能しました。@Khushneetに感謝30分ほど探していました...
csonuryilmaz

すばらしい。このページでのみ有効に機能した。しかし、私はまたIFS="" 、アレイ構築の前に移動する必要がありました。
pkamb 2018年

13

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

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ワードに分割されます。それ以上の単語分割は行いません。

を使用することに"${FILES[*]}"も特別な意味があります、これは配列要素を$ IFSの最初の文字と結合し、1 つの単語を生成します。

裸の使い方${array[@]}${array[*]}単語はスペース(および他に何かに分割して、あなたが終わるだろうので、さらに単語分割への展開の結果科目を$IFS)代わりに、配列要素ごとに1つの単語の。

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

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

3

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

#!/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

文字列を引用しても同じ出力が得られます。


3

次のような配列がある場合:#!/ 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

2

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

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

もちろん、その表現は特定の要件に合わせて採用する必要があります(たとえば*.jpg、すべてまたは2001-09-11*.jpg特定の日の写真のみ)。


0

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

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

0

を使用することにこだわっていない場合は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

以前はIFS値をリセットし、完了したらロールバックしていました。

# backup IFS value
O_IFS=$IFS

# reset IFS value
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"
)

for file in ${FILES[@]}; do
    echo ${file}
done

# rollback IFS value
IFS=${O_IFS}

ループからの可能な出力:

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.