Bash масив з пробілами в елементах


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] = ...? (Редагувати: Я щойно зробив; не працює. Цікаво).
Дан Фего


Усі відповіді тут розбиті на мене за допомогою Cygwin. Це дивно, якщо в іменах файлів є пробіли, період. Я обходжу його, створюючи "масив" у текстовому файлі з переліком усіх елементів, з якими я хочу працювати, та повторюючи рядки у файлі: Форматування відбувається злученням із передбаченими посиланнями, що оточують команду в дужках: IFS = ""; масив = ( find . -maxdepth 1 -type f -iname \*.$1 -printf '%f\n'); для елемента в $ {array [@]}; виконувати елемент echo $; виконано
Алекс Холл

Відповіді:


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, але це буде для всього, що використовує його як ім'я файлу.
Гордон Девіссон

26
Не потрібно перебирати індекси, коли можна перебирати елементи за допомогою for f in "${FILES[@]}".
Марк Едгар

10
@MarkEdgar У мене виникають проблеми з f для $ {FILES [@]}, коли у членів масиву є пробіли. Здається, весь масив знову переосмислюється, пробіли розплющують існуючі члени на два чи більше елементів. Здається, "" дуже важливі
Майкл Шоу

1
Що робить гострий ( #) символ у for ((i = 0; i < ${#FILES[@]}; i++))викладі?
Міхал Вікіан

4
Я відповів на це шість років тому, але я вважаю, що потрібно підрахувати кількість елементів у масиві FILES.
Дан Фего

91

З способом доступу до елементів масиву повинно бути щось не так. Ось як це робиться:

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

З баш-сторінки :

На будь-який елемент масиву можна посилатися, використовуючи $ {name [subscript]}. ... Якщо індексом є @ або *, слово розширюється на всі члени імені. Ці підписки відрізняються лише тоді, коли слово з’являється в подвійних лапках. Якщо слово подвійне цитування, $ {name [*]} розширюється на одне слово зі значенням кожного елемента масиву, розділеного першим символом спеціальної змінної IFS, а $ {name [@]} розширює кожен елемент назва окремим словом .

Звичайно, ви також повинні використовувати подвійні лапки під час доступу до одного члена

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

3
Найяскравіше, найелегантніше рішення у цій купі, хоча слід повторити, що кожен елемент, визначений у масиві, повинен бути цитований.
maverick

Хоча відповідь Дана Фего ефективна, це більш ідіоматичний спосіб поводження з пробілами в елементах.
Даніель Чжан

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) -release (x86_64-apple-darwin18)". Може бути помилка в старій версії bash?
Марк Рібау

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

Це також працювало на мене на баші. Дякую @Khushneet, я шукав його протягом півгодини ...
csonuryilmaz

Чудова, єдина відповідь на цій сторінці, яка працювала. Але мені також довелося перенести IFS="" попереднє будівництво масиву .
pamamb

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) замість одного слова на елемент масиву.

Використання стилю С для циклу також чудово і дозволяє уникнути занепокоєння щодо розбиття слів, якщо вам це не зрозуміло:

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

Іншим рішенням є використання циклу "while" замість циклу "for":

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.