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