Bash: получить количество элементов в массиве, которое является результатом команды `ls`? ${#a[@]} не работает

#arrays #bash #shell #command-line

#массивы #bash #shell #командная строка

Вопрос:

Я получаю 15 самых последних .jpg или .png файлов из текущего каталога в массиве:

images=$(ls -1tr *.jpg *.png | tail -n 15)

Похоже, в данном случае это работает нормально:

for i in ${images[*]}; do echo "Found this image: $i"; done

Он показывает мне 15 строк как:

Найдено это изображение: foo.jpg
Найдено это изображение: bar.png
Найдено это изображение: baz.jpg
(… и т.д….)

Однако, когда я пытаюсь напечатать длину $images массива следующим образом:

echo ${#images[@]}

Я всегда получаю:

1

Несмотря на то, что найдено несколько изображений, и приведенный выше for цикл над $images массивом действительно показывает несколько строк.

Что я делаю не так? Как мне получить количество элементов в массиве?

Комментарии:

1. Вы не определяли массив; вы определили обычную переменную с одной строкой, которая содержит встроенные символы новой строки.

2. И определение массива таким образом — images=( $(ls -1tr *.jpg *.png | tail -n 15) ) — может привести к сбою, поскольку замена команды может привести как к разбиению слов, так и к расширению имени пути.

3.Видите BashFAQ /003 (Как я могу сортировать или сравнивать файлы на основе какого-либо атрибута метаданных (время последнего / старейшего изменения, размер и т.д.)?).

Ответ №1:

Вы правильно получаете количество элементов, и оно действительно равно 1.

Поскольку вы знаете JS, вот ваш код на JavaScript, чтобы увидеть, что происходит на самом деле:

 // Assign all filenames to a single index
var images = ["foo.jpgnbar.jpgnbaz.jpg"]

// Join all the elements on spaces, then split them up on whitespace
var elements = images.join(" ").split(/[ tn]/);

for (var i in elements) {
  console.log("Found this image: "   elements[i]);
}
console.log("Array length: "   images.length);
  

Вывод:

 Found this image: foo.jpg
Found this image: bar.jpg
Found this image: baz.jpg
Array length: 1
  

Вот что вы вместо этого хотели сделать в Bash:

 images=( $(ls -1tr *.jpg *.png | tail -n 15) )
for i in "${images[@]}"; do echo "Found this image: $i"; done
echo "${#images[@]}"
  

Хотя разбор ls выходных данных считается хрупким: приведенный выше код по-прежнему разбивается на пробелы, поэтому My Image.jpg он превратится в My и Image.jpg .

К сожалению, простой и удобной замены, когда вам нужны файлы, отсортированные по дате изменения, нет, но это позволяет избежать проблем, когда файлы содержат * пробелы или (Bash 4 ):

 mapfile -t images < <(ls -tr *.jpg *.png | tail -n 15)
for i in "${images[@]}"; do echo "Found this image: $i"; done
echo "${#images[@]}"
  

Комментарии:

1. Будьте внимательны: последнее по-прежнему завершается ошибкой, если имена файлов могут содержать новые строки (что случается редко, но стоит упомянуть для полноты картины).