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

Я пытаюсь построить массив в 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)

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

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

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

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

Ответ 1

Я думаю, что проблема может быть частично связана с тем, как вы обращаетесь к элементам. Если я делаю простой 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"

Ответ 2

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

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

Из bash manpage:

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

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

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

Ответ 3

Вам нужно использовать 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 = "." Надеюсь, это поможет вам:)

Ответ 4

Я согласен с другими, что, скорее всего, вы получаете доступ к элементам, которые являются проблемой. Цитирование имен файлов в назначении массива корректно:

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

Ответ 5

Работа экранирования.

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

Цитирование строк также дает тот же результат.

Ответ 6

Не совсем ответ на вопрос о цитировании/исключении исходного вопроса, но, вероятно, что-то, что на самом деле было бы более полезным для op:

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

Если, конечно, выражение должно быть принято к конкретному требованию (например, *.jpg для всех или 2001-09-11*.jpg только для изображений определенного дня).

Ответ 7

Если у вас есть ваш массив, как это: #!/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

Ответ 8

Другое решение использует цикл "while" вместо цикла "for":

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

Ответ 9

Если вы не застряли в использовании 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