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)

Как видите, в середине каждого имени файла есть пробел.

Я попытался обернуть каждое имя в кавычки и экранировать пробел с помощью обратной косой черты, ни один из которых не работает.

Когда я пытаюсь получить доступ к элементам массива, он продолжает обрабатывать пространство как ограничитель элементов.

Как я могу правильно захватить имена файлов с пробелом внутри имени?

abelenky
источник
Вы пытались добавить файлы по старинке? Как FILES[0] = ...? (Изменить: я только что сделал; не работает. Интересно).
Дан Фего
POSIX: stackoverflow.com/questions/2936922/…
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
Все ответы здесь ломаются для меня, используя Cygwin. Это делает странные вещи, если в именах файлов есть пробелы, точка. Я работаю над этим, создавая «массив» в текстовом файле со списком всех элементов, с которыми я хочу работать, и перебирая строки в файле: форматирование - это гадость с предполагаемыми галочками здесь, окружающими команду в скобках: IFS = ""; массив = ( find . -maxdepth 1 -type f -iname \*.$1 -printf '%f\n'); для элемента в $ {array [@]}; do echo $ element; сделано
Алекс Холл

Ответы:

122

Я думаю, что проблема может быть частично связана с тем, как вы получаете доступ к элементам. Если я делаю простое 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, но это будет для всего, что использует его в качестве имени файла.
Гордон Дэвиссон
26
Нет необходимости перебирать индексы, когда вы можете перебирать элементы с помощью for f in "${FILES[@]}".
Марк Эдгар
10
@MarkEdgar У меня проблемы с for для f в $ {FILES [@]}, когда члены массива имеют пробелы. Кажется, что весь массив снова интерпретируется заново, при этом пробелы разбивают ваши существующие элементы на два или более элементов. Кажется, "" очень важны
Майкл Шоу
1
Что делает #в for ((i = 0; i < ${#FILES[@]}; i++))заявлении символ sharp ( ) ?
Михал Викиан
4
Я ответил на это шесть лет назад, но я считаю, что это подсчитать количество элементов в массиве FILES.
Дэн Фего
91

Должно быть что-то не так с доступом к элементам массива. Вот как это делается:

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

Из справочной страницы bash :

На любой элемент массива можно ссылаться, используя $ {name [subscript]}. ... Если индекс равен @ или *, слово распространяется на всех членов имени. Эти индексы отличаются только тогда, когда слово появляется в двойных кавычках. Если слово заключено в двойные кавычки, $ {name [*]} раскрывается в одно слово, в котором значение каждого члена массива разделяется первым символом специальной переменной IFS, а $ {name [@]} раскрывает каждый элемент имя для отдельного слова .

Конечно, вы также должны использовать двойные кавычки при доступе к одному члену

cp "${files[0]}" /tmp
user123444555621
источник
3
Самое чистое и элегантное решение в этой связке, хотя следует повторить, что каждый элемент, определенный в массиве, должен быть заключен в кавычки.
Индивидуалист
Хотя ответ Дана Фего эффективен, это более идиоматический способ обработки пробелов в элементах.
Даниэль Чжан
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)». Может быть, ошибка в старой версии 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 = "." Надеюсь, это поможет вам :)

Khushneet
источник
3
Мне пришлось переместить IFS = "" до назначения массива, но это правильный ответ.
ограбить
Я использую несколько массивов для разбора информации, и я получу эффект IFS = "", работающий только в одном из них. Как только я использую IFS = "", все остальные массивы перестают анализироваться соответственно. Есть намеки на это?
Пауло Педросо
Пауло, посмотрите другой ответ, который может быть лучше для вашего случая: stackoverflow.com/a/9089186/1041319 . Не пробовал IFS = "", и кажется, что он решает ее элегантно - но ваш пример показывает, почему в некоторых случаях могут возникнуть проблемы. Может быть возможно установить IFS = "" в одной строке, но это все еще может быть более запутанным, чем другое решение.
arntg
У меня это тоже сработало на bash. Спасибо @Khushneet, я искал это в течение получаса ...
csonuryilmaz
Отлично, только ответ на этой странице сработал. Но мне также пришлось передвинуть IFS="" до построения массива .
Пкамб
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 в стиле 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
Jonni2016aa
источник
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, различная обработка пробелов в именах файлов является одним из преимуществ оболочки fish . Рассмотрим каталог, который содержит два файла: «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 разбивает вывод команд на новые строки, а не пробелы.

Если у вас есть случай, когда вы хотите разделить пробелы, а не переводы строк, для 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

Мадан Сапкота
источник