ทุบตีอาร์เรย์ที่มีช่องว่างในองค์ประกอบ


150

ฉันพยายามสร้างอาร์เรย์ด้วยการทุบตีชื่อไฟล์จากกล้องของฉัน:

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] = ...ไหม (แก้ไข: ฉันเพิ่งทำไม่ได้ผลน่าสนใจ)
Dan Fego


คำตอบทั้งหมดที่นี่แบ่งให้ฉันโดยใช้ Cygwin มันทำสิ่งแปลก ๆ หากมีช่องว่างในชื่อไฟล์, ระยะเวลา ฉันทำงานโดยการสร้าง "array" ในรายการไฟล์ข้อความขององค์ประกอบทั้งหมดที่ฉันต้องการใช้งานและวนซ้ำเหนือบรรทัดในไฟล์: การจัดรูปแบบเป็นการแม็คด้วย backticks ตั้งใจที่นี่ล้อมรอบคำสั่งในวงเล็บ: IFS = ""; array = ( find . -maxdepth 1 -type f -iname \*.$1 -printf '%f\n'); สำหรับองค์ประกอบใน $ {array [@]}; ทำ echo $ element; เสร็จแล้ว
Alex Hall

คำตอบ:


121

ฉันคิดว่าปัญหาอาจเป็นส่วนหนึ่งกับวิธีที่คุณเข้าถึงองค์ประกอบ ถ้าฉันทำง่ายfor elem in $FILESฉันพบปัญหาเช่นเดียวกับคุณ อย่างไรก็ตามถ้าฉันเข้าถึงอาร์เรย์ผ่านดัชนีของมันเช่นนั้นมันใช้งานได้ถ้าฉันเพิ่มองค์ประกอบเป็นตัวเลขหรือใช้ Escape:

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

26
for f in "${FILES[@]}"มันไม่จำเป็นที่จะห่วงมากกว่าดัชนีเมื่อคุณสามารถห่วงมากกว่าองค์ประกอบด้วย
Mark Edgar

10
@MarkEdgar ฉันประสบปัญหากับสำหรับ f ใน $ {FILES [@]}เมื่อสมาชิกอาร์เรย์มีช่องว่าง ดูเหมือนว่าอาเรย์ทั้งหมดจะถูกตีความใหม่อีกครั้งด้วยช่องว่างที่กระจายสมาชิกที่มีอยู่ของคุณออกเป็นสององค์ประกอบหรือมากกว่า ดูเหมือนว่า "" มีความสำคัญมาก
Michael Shaw

1
#สัญลักษณ์คม ( ) ทำอะไรในfor ((i = 0; i < ${#FILES[@]}; i++))แถลงการณ์?
Michal Vician

4
ฉันตอบนี้หกปีที่ผ่านมา แต่ผมเชื่อว่ามันจะได้รับการนับจำนวนขององค์ประกอบในไฟล์อาร์เรย์
Dan Fego

91

จะต้องมีบางอย่างผิดปกติกับวิธีที่คุณเข้าถึงรายการของอาร์เรย์ นี่คือวิธีการ:

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

จากmanpage ทุบตี :

องค์ประกอบใด ๆ ของอาเรย์อาจถูกอ้างอิงโดยใช้ $ {name [subscript]} ... หากตัวห้อยเป็น @ หรือ * คำนั้นจะขยายไปยังสมาชิกทุกคนของชื่อ ตัวห้อยเหล่านี้แตกต่างกันเฉพาะเมื่อคำนั้นปรากฏภายในเครื่องหมายคำพูดคู่ หากคำนี้เป็นเครื่องหมายคำพูดสองครั้ง $ {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[@]}- ช่องว่างทำให้เกิดความสับสนในการขยายตัวและสำหรับการพยายามใช้คำแต่ละคำ
arntg

สิ่งนี้ไม่ได้ผลสำหรับฉันใน macOS 10.14.4 ซึ่งใช้ "GNU bash รุ่น 3.2.57 (1) - ปล่อย (x86_64-apple-darwin18)" อาจเป็นข้อผิดพลาดในทุบตีรุ่นเก่ากว่า?
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 = "" ไปก่อนการกำหนดอาร์เรย์ แต่นี่เป็นคำตอบที่ถูกต้อง
ปล้น

ฉันใช้หลายอาร์เรย์ในการวิเคราะห์ข้อมูลและฉันจะมีผลกระทบจาก IFS = "" ทำงานในหนึ่งเดียวเท่านั้น เมื่อฉันใช้ IFS = "" อาร์เรย์อื่นทั้งหมดหยุดการแยกวิเคราะห์ตามลำดับ มีคำแนะนำเกี่ยวกับเรื่องนี้หรือไม่?
เปาโลเปโดรโซ

เปาโลดูคำตอบที่นี่อีกซึ่งอาจจะดีกว่าสำหรับกรณีของคุณ: stackoverflow.com/a/9089186/1041319 ยังไม่ได้ลอง IFS = "" และดูเหมือนว่ามันจะแก้ปัญหาได้อย่างสวยงาม - แต่ตัวอย่างของคุณแสดงว่าทำไมบางครั้งอาจพบปัญหาในบางกรณี อาจเป็นไปได้ที่จะตั้งค่า IFS = "" ในบรรทัดเดียว แต่อาจยังสับสนมากกว่าโซลูชันอื่น
arntg

มันทำงานได้ดีสำหรับฉันด้วยการทุบตี ขอบคุณ @Kushusheet ฉันกำลังค้นหาอยู่ครึ่งชั่วโมง ...
csonuryilmaz

เยี่ยมมากมีเพียงคำตอบในหน้านี้ที่ใช้งานได้ แต่ฉันยังมีการย้ายก่อนการก่อสร้างอาร์เรย์IFS=""
pkamb

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[@]}"แยกอาร์เรย์ออกเป็นหนึ่งคำต่อองค์ประกอบอาร์เรย์ มันไม่ได้แยกคำใด ๆ ออกจากนั้น

การใช้"${FILES[*]}"ยังมีความหมายพิเศษ แต่จะรวมองค์ประกอบของอาเรย์กับอักขระตัวแรกของ $ IFS ส่งผลให้หนึ่งคำซึ่งอาจไม่ใช่สิ่งที่คุณต้องการ

การใช้คำเปล่า${array[@]}หรือ${array[*]}หัวเรื่องผลของการขยายตัวนั้นไปสู่การแยกคำต่อไปดังนั้นคุณจะได้คำที่แยกออกจากช่องว่าง (และสิ่งอื่น ๆ$IFS) แทนที่จะเป็นหนึ่งคำต่อหนึ่งองค์ประกอบอาร์เรย์

การใช้รูปแบบ C สำหรับลูปนั้นก็ใช้ได้เช่นกันและหลีกเลี่ยงการกังวลเกี่ยวกับการแบ่งคำหากคุณไม่ชัดเจน:

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

ไม่ใช่คำตอบที่ถูกต้องสำหรับปัญหาการอ้างอิง / การหลบหนีของคำถามเดิม แต่อาจเป็นสิ่งที่จริง ๆ แล้วมีประโยชน์มากกว่าสำหรับ op:

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

แน่นอนว่าการแสดงออกจะต้องถูกนำไปใช้กับข้อกำหนดเฉพาะ (เช่น*.jpgสำหรับทั้งหมดหรือ2001-09-11*.jpgเฉพาะภาพในบางวัน)


0

โซลูชันอื่นกำลังใช้การวนรอบ "ในขณะ" แทนการวนรอบ "สำหรับ":

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

0

ถ้าคุณไม่ได้ติดอยู่กับการใช้bashการจัดการที่แตกต่างกันของช่องว่างในชื่อไฟล์เป็นหนึ่งในประโยชน์ของเปลือกปลา พิจารณาไดเรกทอรีที่มีสองไฟล์: "a b.txt" และ "b c.txt" นี่คือการคาดเดาที่สมเหตุสมผลในการประมวลผลรายการไฟล์ที่สร้างจากคำสั่งอื่นด้วย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มีไวยากรณ์ที่สามารถอ่านได้สำหรับ:

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.