Mảng Bash với khoảng trắng trong các phần tử


150

Tôi đang cố gắng xây dựng một mảng trong tên tập tin từ máy ảnh của mình:

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)

Như bạn có thể thấy, có một khoảng trắng ở giữa mỗi tên tệp.

Tôi đã thử gói từng tên trong dấu ngoặc kép và thoát khỏi khoảng trắng bằng dấu gạch chéo ngược, cả hai đều không hoạt động.

Khi tôi cố gắng truy cập các phần tử mảng, nó tiếp tục coi không gian là phần tử tổng hợp.

Làm thế nào tôi có thể chụp đúng tên tệp với khoảng trắng bên trong tên?


Bạn đã thử thêm các tập tin theo cách lỗi thời? Giống như FILES[0] = ...? (Chỉnh sửa: Tôi vừa làm; không hoạt động. Thú vị).
Dan Fego


Tất cả các câu trả lời ở đây đều bị phá vỡ đối với tôi khi sử dụng Cygwin. Nó làm những điều kỳ lạ nếu có không gian trong tên tập tin, thời gian. Tôi làm việc xung quanh nó bằng cách tạo một "mảng" trong danh sách tệp văn bản của tất cả các phần tử tôi muốn làm việc và lặp lại các dòng trong tệp: Định dạng là mucking với các backticks dự định ở đây xung quanh lệnh trong ngoặc đơn: IFS = ""; mảng = ( find . -maxdepth 1 -type f -iname \*.$1 -printf '%f\n'); cho phần tử trong $ {mảng [@]}; làm phần tử echo $; xong
Alex Hall

Câu trả lời:


121

Tôi nghĩ vấn đề có thể là một phần với cách bạn truy cập các yếu tố. Nếu tôi làm một cách đơn giản for elem in $FILES, tôi gặp vấn đề tương tự như bạn. Tuy nhiên, nếu tôi truy cập mảng thông qua các chỉ mục của nó, như vậy, nó sẽ hoạt động nếu tôi thêm các phần tử bằng số hoặc bằng các lối thoát:

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

Bất kỳ tuyên bố nào trong số $FILESnày nên hoạt động:

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)

hoặc là

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

hoặc là

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
Lưu ý rằng bạn nên sử dụng dấu ngoặc kép khi bạn sử dụng các phần tử mảng (ví dụ echo "${FILES[$i]}"). Nó không quan trọng echo, nhưng nó sẽ cho bất cứ thứ gì sử dụng nó làm tên tệp.
Gordon Davisson

26
Không cần thiết phải lặp qua các chỉ mục khi bạn có thể lặp qua các phần tử với for f in "${FILES[@]}".
Đánh dấu Edgar

10
@MarkEdgar tôi gặp vấn đề với f trong $ {FILES [@]} khi các thành viên mảng có khoảng trắng. Dường như toàn bộ mảng được diễn giải lại một lần nữa, với các khoảng trắng chia thành viên hiện tại của bạn thành hai hoặc nhiều phần tử. Có vẻ như "" rất quan trọng
Michael Shaw

1
#Biểu tượng sharp ( ) làm gì trong for ((i = 0; i < ${#FILES[@]}; i++))câu lệnh?
Michal Vician

4
Tôi trả lời này sáu năm trước nhưng tôi tin rằng đó là để có được đếm số lượng các yếu tố trong FILES mảng.
Dan Fego

91

Có điều gì đó không đúng với cách bạn truy cập vào các mục của mảng. Đây là cách nó được thực hiện:

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

Từ trang bash :

Bất kỳ phần tử nào của một mảng có thể được tham chiếu bằng $ {name [subscript]}. ... Nếu đăng ký là @ hoặc *, từ này sẽ mở rộng cho tất cả các thành viên của tên. Các mục con này chỉ khác nhau khi từ xuất hiện trong dấu ngoặc kép. Nếu từ được trích dẫn kép, $ {name [*]} sẽ mở rộng thành một từ duy nhất với giá trị của từng thành viên mảng được phân tách bằng ký tự đầu tiên của biến đặc biệt IFS và $ {name [@]} mở rộng từng phần tử của đặt tên cho một từ riêng biệt .

Tất nhiên, bạn cũng nên sử dụng dấu ngoặc kép khi truy cập vào một thành viên

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

3
Giải pháp sạch nhất, thanh lịch nhất trong bó này, tuy nhiên nên lặp lại rằng mỗi phần tử được xác định trong mảng sẽ được trích dẫn.
maverick

Trong khi câu trả lời của Dan Fego có hiệu quả, đây là cách thành ngữ hơn để xử lý các khoảng trắng trong các phần tử.
Daniel Zhang

3
Đến từ các ngôn ngữ lập trình khác, thuật ngữ từ đoạn trích đó thực sự khó hiểu. Cộng với cú pháp là trở ngại. Tôi sẽ vô cùng biết ơn nếu bạn có thể đi sâu hơn một chút? Đặc biệtexpands to a single word with the value of each array member separated by the first character of the IFS special variable
CL22

1
Vâng, đồng ý các trích dẫn kép đang giải quyết nó và điều này là tốt hơn so với các giải pháp khác. Để giải thích thêm - hầu hết những người khác chỉ thiếu dấu ngoặc kép. Bạn đã hiểu đúng : for elem in "${files[@]}", trong khi chúng có for elem in ${files[@]}- vì vậy các khoảng trắng nhầm lẫn giữa việc mở rộng và để thử chạy trên các từ riêng lẻ.
arntg

Điều này không hoạt động với tôi trong macOS 10.14.4, sử dụng "GNU bash, phiên bản 3.2.57 (1) -release (x86_64-apple-darwin18)". Có lẽ một lỗi trong phiên bản cũ của bash?
Mark Ribau

43

Bạn cần sử dụng IFS để dừng không gian làm dấu phân cách phần tử.

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

Nếu bạn muốn tách biệt trên cơ sở. sau đó chỉ cần làm IFS = "." Hy vọng nó sẽ giúp bạn :)


3
Tôi đã phải di chuyển IFS = "" trước khi gán mảng nhưng đây là câu trả lời đúng.
cướp

Tôi đang sử dụng một số mảng để phân tích thông tin và tôi sẽ có tác dụng của IFS = "" chỉ hoạt động trong một trong số đó. Khi tôi sử dụng IFS = "", tất cả các mảng khác dừng phân tích cú pháp tương ứng. Bất kỳ gợi ý về điều này?
Paulo Pedroso

Paulo, xem một câu trả lời khác ở đây có thể tốt hơn cho trường hợp của bạn: stackoverflow.com/a/9089186/1041319 . Chưa thử IFS = "" và dường như nó giải quyết vấn đề một cách thanh lịch - nhưng ví dụ của bạn cho thấy tại sao một người có thể gặp phải vấn đề trong một số trường hợp. Có thể đặt IFS = "" trên một dòng duy nhất, nhưng nó vẫn có thể gây nhầm lẫn hơn so với giải pháp khác.
arntg

Nó cũng làm việc cho tôi trên bash. Cảm ơn @Khushneet tôi đã tìm kiếm nó trong nửa giờ ...
csonuryilmaz

Tuyệt vời, chỉ trả lời trên trang này mà làm việc. Nhưng tôi cũng đã phải di chuyển IFS="" trước khi xây dựng mảng .
pkamb

13

Tôi đồng ý với những người khác rằng có khả năng bạn đang truy cập các yếu tố gây ra sự cố. Trích dẫn tên tệp trong phân công mảng là chính xác:

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

Sử dụng dấu ngoặc kép xung quanh bất kỳ mảng nào của biểu mẫu sẽ "${FILES[@]}"chia mảng thành một từ cho mỗi phần tử mảng. Nó không thực hiện bất kỳ sự phân tách từ nào ngoài điều đó.

Việc sử dụng "${FILES[*]}"cũng có một ý nghĩa đặc biệt, nhưng nó kết hợp các thành phần mảng với ký tự đầu tiên của $ IFS, dẫn đến một từ, có lẽ không phải là điều bạn muốn.

Sử dụng một khoảng trống ${array[@]}hoặc ${array[*]}chủ đề kết quả của việc mở rộng đó để phân tách từ hơn nữa, vì vậy bạn sẽ kết thúc bằng các từ được phân tách trên khoảng trắng (và bất cứ thứ gì khác trong $IFS) thay vì một từ cho mỗi thành phần mảng.

Sử dụng vòng lặp kiểu C cũng tốt và tránh lo lắng về việc tách từ nếu bạn không rõ ràng về nó:

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

3

Thoát công trình.

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

Đầu ra:

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

Trích dẫn các chuỗi cũng tạo ra đầu ra tương tự.


3

Nếu bạn có mảng của mình như thế này: #! / Bin / bash

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

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

Bạn sẽ nhận được:

Debian
Red
Hat
Ubuntu
Suse

Tôi không biết tại sao nhưng vòng lặp phá vỡ các khoảng trắng và đặt chúng dưới dạng một mục riêng lẻ, thậm chí bạn bao quanh nó bằng dấu ngoặc kép.

Để giải quyết vấn đề này, thay vì gọi các phần tử trong mảng, bạn gọi các chỉ mục, lấy chuỗi đầy đủ được gói trong dấu ngoặc kép. Nó phải được bọc trong dấu ngoặc kép!

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

Sau đó, bạn sẽ nhận được:

Debian
Red Hat
Ubuntu
Suse

2

Không chính xác là một câu trả lời cho vấn đề trích dẫn / thoát của câu hỏi ban đầu nhưng có lẽ một cái gì đó thực sự sẽ hữu ích hơn cho op:

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

Tất nhiên, biểu thức sẽ phải được chấp nhận theo yêu cầu cụ thể (ví dụ: *.jpgcho tất cả hoặc 2001-09-11*.jpgchỉ cho các hình ảnh của một ngày nhất định).


0

Một giải pháp khác là sử dụng vòng lặp "while" thay vì vòng lặp "for":

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

0

Nếu bạn không bị mắc kẹt trong việc sử dụng bash, việc xử lý các khoảng trắng khác nhau trong tên tệp là một trong những lợi ích của vỏ cá . Hãy xem xét một thư mục chứa hai tệp: "a b.txt" và "b c.txt". Đây là một phỏng đoán hợp lý khi xử lý danh sách các tệp được tạo từ một lệnh khác bash, nhưng nó không thành công do khoảng trắng trong tên tệp bạn đã trải nghiệm:

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

Với fish, cú pháp gần giống nhau, nhưng kết quả là những gì bạn mong đợi:

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

Nó hoạt động khác nhau vì cá chia đầu ra của các lệnh trên dòng mới, không phải khoảng trắng.

Nếu bạn có trường hợp bạn muốn phân tách trên khoảng trắng thay vì dòng mới, fishcó một cú pháp rất dễ đọc cho điều đó:

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

0

Tôi đã sử dụng để thiết lập lại giá trị IFS và rollback khi hoàn tất.

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

Đầu ra có thể từ vòng lặp:

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.