Öğelerde boşluk içeren Bash dizisi


151

Kameramdan dosya adlarının bashında bir dizi oluşturmaya çalışıyorum:

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)

Gördüğünüz gibi, her dosya adının ortasında bir boşluk var.

Her adı tırnak içine almayı ve boşluktan ters eğik çizgiyle kaçmayı denedim, ikisi de işe yaramıyor.

Dizi elemanlarına erişmeye çalıştığımda, alanı eleman sınırlayıcı olarak görmeye devam ediyor.

Adın içinde boşluk bulunan dosya adlarını nasıl düzgün bir şekilde yakalayabilirim?


Dosyaları eski usul şekilde eklemeyi denediniz mi? Beğen FILES[0] = ...? (Düzenleme: Yeni yaptım; işe yaramıyor. İlginç).
Dan Fego


Buradaki tüm cevaplar Cygwin'i kullanarak benim için bozuldu. Dosya adlarında nokta varsa garip şeyler yapar. Bir metin dosyasında birlikte çalışmak istediğim tüm öğeleri listeleyen bir "dizi" oluşturarak ve dosyadaki satırlar üzerinde yineleyerek bu sorunu çözüyorum: Biçimlendirme, burada parantez içindeki komutu çevreleyen amaçlanan geri işaretlerle karıştırılıyor: IFS = ""; dizi = ( find . -maxdepth 1 -type f -iname \*.$1 -printf '%f\n'); $ {dizi [@]} içindeki öğe için; do echo $ element; bitti
Alex Hall

Yanıtlar:


125

Sanırım sorun kısmen öğelere nasıl eriştiğinizle ilgili olabilir. Basit bir şey yaparsam for elem in $FILES, sizinle aynı sorunu yaşarım. Bununla birlikte, diziye indisleri aracılığıyla erişirsem, bu şekilde, öğeleri sayısal olarak veya kaçışlarla eklersem çalışır:

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

Bu beyanlardan herhangi biri $FILESçalışmalıdır:

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)

veya

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")

veya

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"

7
Dizi öğelerini kullanırken çift tırnak kullanmanız gerektiğini unutmayın (örneğin echo "${FILES[$i]}"). Önemli değil echo, ama dosya adı olarak kullanan her şey için önemli.
Gordon Davisson

28
İle elemanlar üzerinde döngü yapabildiğiniz zaman indeksler üzerinde döngü oluşturmanıza gerek yoktur for f in "${FILES[@]}".
Mark Edgar

10
@MarkEdgar Dizi üyelerinde boşluklar olduğunda $ {FILES [@]} içinde f için sorun yaşıyorum . Görünüşe göre tüm dizi, mevcut üyelerinizi iki veya daha fazla öğeye ayıran boşluklarla yeniden yorumlanıyor. Görünüşe göre "" çok önemli
Michael Shaw

1
Sharp ( #) sembolü for ((i = 0; i < ${#FILES[@]}; i++))ifadede ne işe yarar?
Michal Vician

4
Altı yıl önce bu cevap ama olsun inanıyorum sayımı dizi DOSYALARI elemanların sayısının.
Dan Fego

96

Dizinin öğelerine erişme şeklinizde bir sorun olmalı. İşte böyle yapılır:

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

Gönderen bash manpage :

Bir dizinin herhangi bir öğesi $ {isim [indis]} kullanılarak belirtilebilir. ... Alt simge @ veya * ise, kelime ismin tüm üyelerine genişler. Bu alt simgeler, yalnızca sözcük çift tırnak içinde göründüğünde farklılık gösterir. Sözcük çift tırnak içine alınmışsa, $ {isim [*]}, IFS özel değişkeninin ilk karakteriyle ayrılmış her bir dizi üyesinin değeri ile tek bir kelimeye genişler ve $ {isim [@]}, adı ayrı bir kelimeye .

Tabii ki, tek bir üyeye erişirken de çift tırnak kullanmalısınız

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

3
Bu gruptaki en temiz, en zarif çözüm, ancak dizide tanımlanan her bir öğenin alıntılanması gerektiğini yeniden yinelemelidir.
maverick

Dan Fego'nun cevabı etkili olsa da bu, öğelerdeki boşlukları ele almanın daha deyimsel bir yoludur.
Daniel Zhang

3
Diğer programlama dillerinden gelince, bu alıntıdaki terminolojiyi anlamak gerçekten zor. Ayrıca sözdizimi şaşırtıcıdır. Biraz daha girebilirseniz çok minnettar olurum. Özellikleexpands to a single word with the value of each array member separated by the first character of the IFS special variable
CL22

1
Evet, çift tırnakların sorunu çözdüğüne katılıyorum ve bu diğer çözümlerden daha iyidir. Daha fazla açıklamak gerekirse, diğerlerinin çoğunda çift tırnak yok. Doğru olanı anladınız: for elem in "${files[@]}"onlar varken for elem in ${files[@]}- boşluklar genişlemeyi karıştırır ve tek tek kelimeler üzerinde koşma denemeleri için.
arntg

Bu benim için "GNU bash, sürüm 3.2.57 (1) -release (x86_64-apple-darwin18)" kullanan macOS 10.14.4'te çalışmıyor. Bash'in eski sürümünde bir hata olabilir mi?
Mark Ribau

44

Alanı öğe sınırlayıcı olarak durdurmak için IFS kullanmanız gerekir.

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

Bazında ayırmak istiyorsanız. o zaman sadece IFS = "." Umarım size yardımcı olur :)


3
IFS = "" değerini dizi atamasından önceye taşımak zorunda kaldım ama bu doğru cevap.
soymak

Bilgileri ayrıştırmak için birkaç dizi kullanıyorum ve bunlardan yalnızca birinde çalışan IFS = "" etkisine sahip olacağım. IFS = "" kullandığımda diğer tüm diziler buna göre ayrıştırmayı durdurur. Bununla ilgili herhangi bir ipucu var mı?
Paulo Pedroso

Paulo, burada sizin durumunuz için daha iyi olabilecek başka bir cevaba bakın: stackoverflow.com/a/9089186/1041319 . IFS = "" denemediniz ve görünüşe göre onu zarif bir şekilde çözüyor - ama sizin örneğiniz bazı durumlarda neden sorunlarla karşılaşılabileceğini gösteriyor. IFS = "" değerini tek bir hatta ayarlamak mümkün olabilir, ancak yine de diğer çözümden daha kafa karıştırıcı olabilir.
arntg

Bash'de benim için de çalıştı. Teşekkürler @Khushneet Yarım saattir
arıyordum

Harika, sadece işe yarayan bu sayfadaki cevap. Ama aynı zamanda IFS="" dizi yapımından önce de hareket etmem gerekiyordu .
pkamb

14

Sorun olan öğelere nasıl eriştiğiniz konusunda başkalarına katılıyorum. Dizi atamasında dosya adlarından alıntı yapmak doğrudur:

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

Formun herhangi bir dizisinin etrafında çift tırnak işareti kullanmak "${FILES[@]}", diziyi dizi öğesi başına bir kelimeye böler. Bunun ötesinde herhangi bir kelime ayırma yapmaz.

Kullanmanın "${FILES[*]}"da özel bir anlamı vardır, ancak dizi elemanlarını $ IFS'nin ilk karakteriyle birleştirerek tek bir kelime ile sonuçlanır , ki bu muhtemelen istediğiniz şey değildir.

Çıplak ${array[@]}veya ${array[*]}konu bu genişlemenin sonucunu daha fazla kelime bölmeye tabi tutar, bu nedenle $IFSdizi öğesi başına bir kelime yerine boşluklara (ve içindeki herhangi bir şeye ) bölünmüş kelimelerle sonuçlanırsınız.

C tarzı bir döngü kullanmak da iyidir ve net değilseniz kelime bölme konusunda endişelenmenizi önler:

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

5

Diziniz şöyle olsaydı: #! / Bin / bash

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

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

Alacaksın:

Debian
Red
Hat
Ubuntu
Suse

Nedenini bilmiyorum ama döngü boşlukları parçalara ayırıyor ve onları tek tek bir öğe olarak yerleştiriyor, hatta tırnak içine alsanız bile.

Bunu aşmak için, dizideki öğeleri çağırmak yerine, tırnak içine alınmış dizenin tamamını alan dizinleri çağırırsınız. Tırnak işaretleri arasına alınmalıdır!

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

Sonra alacaksın:

Debian
Red Hat
Ubuntu
Suse

3

Kaçış işleri.

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

Çıktı:

$ ./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

Dizelerin alıntılanması da aynı çıktıyı üretir.


2

Orijinal sorunun alıntılama / kaçma sorununa tam olarak bir cevap değil, muhtemelen operasyon için daha faydalı olacak bir şey:

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

Tabi ki ifadenin belirli bir gerekliliğe uyarlanması gerektiğinde (örneğin *.jpgtümü için veya 2001-09-11*.jpgsadece belirli bir günün resimleri için).


1

IFS değerini sıfırlar ve bittiğinde geri alırdım.

# 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}

Döngüden olası çıktı:

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


0

Diğer bir çözüm ise "while" döngüsü yerine "for" döngüsü kullanmaktır:

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

0

Kullanmaya takılıp bashkalmadıysanız, dosya adlarındaki boşlukların farklı şekilde ele alınması, balık kabuğunun faydalarından biridir . İki dosya içeren bir dizin düşünün: "a b.txt" ve "b c.txt". Başka bir komutla oluşturulan dosyaların bir listesini işlemek için makul bir tahmin var bash, ancak karşılaştığınız dosya adlarındaki boşluklar nedeniyle başarısız oluyor:

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

İle fishsözdizimi neredeyse aynıdır, ancak sonuç beklediğiniz şeydir:

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

Farklı çalışır çünkü fish komutların çıktısını boşluklara değil satırsonlarına böler.

Satırsonu yerine boşluklara bölmek istediğiniz bir durumunuz varsa, fishbunun için çok okunabilir bir sözdizimi vardır:

for f in (ls *.txt | string split " "); echo $f; end
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.