Итерации по списку имен файлов, чтобы они были созданы в bash

Разбор вывода ls для перебора списка файлов bad. Итак, как мне нужно перебирать список файлов, чтобы они были сначала созданы? Я просмотрел несколько вопросов здесь на SO, и все они, похоже, разбираются ls.

Вложенная ссылка предлагает:

Все становится сложнее, если вам нужна определенная сортировка, которая только ls может выполнять, например, упорядочивание на mtime. Если вы хотите, чтобы новый файл в каталоге, не используйте ls -t | head -1 - читайте Bash FAQ 99 вместо этого. Если вам действительно нужен список всех файлов в каталоге в порядке mtime, чтобы вы могли обрабатывать их последовательно, переключитесь на perl, и ваша программа perl сделает свое открытие каталога и сортировка. Затем выполните обработку в программе perl, или - в худшем случае сценарий - программа perl выплевывает имена файлов с помощью NUL разделители.

Еще лучше, укажите время модификации в имени файла, в YYYYMMDD формат, так что порядок glob также является mtime-порядком. Тогда вам не нужны ls или perl или что угодно. (Подавляющее большинство случаев, когда люди хотят старейший или новейший файл в каталоге можно решить, просто выполняя это.)

Означает ли это, что существует нет собственный способ сделать это в bash? У меня нет права изменять имя файла, чтобы включить в него время. Мне нужно запланировать script в cron, который будет запускаться каждые 5 минут, генерировать массив, содержащий все файлы в определенном каталоге, упорядоченные по времени их создания, и выполнять некоторые действия над именами файлов и перемещать их в другое место.

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

files=( $(ls -1tr) ) 

Я могу написать perl script, который будет делать то, что мне нужно, но я был бы признателен, если кто-то может предложить правильный способ сделать это в bash. Портативный вариант был бы замечательным, но решение с использованием последних утилит GNU тоже не будет проблемой.

Ответы

Ответ 1

sorthelper=();
for file in *; do
    # We need something that can easily be sorted.
    # Here, we use "<date><filename>".
    # Note that this works with any special characters in filenames

    sorthelper+=("$(stat -n -f "%Sm%N" -t "%Y%m%d%H%M%S" -- "$file")"); # Mac OS X only
    # or
    sorthelper+=("$(stat --printf "%Y    %n" -- "$file")"); # Linux only
done;

sorted=();
while read -d $'\0' elem; do
    # this strips away the first 14 characters (<date>) 
    sorted+=("${elem:14}");
done < <(printf '%s\0' "${sorthelper[@]}" | sort -z)

for file in "${sorted[@]}"; do
    # do your stuff...
    echo "$file";
done;

Помимо sort и stat, все команды являются собственными командами Bash (встроенными) *. Если вы действительно хотите, вы можете реализовать свой собственный sort только с помощью встроенных встроенных Bash, но я не вижу способа избавиться от stat.

Важными частями являются read -d $'\0', printf '%s\0' и sort -z. Все эти команды используются с опциями нулевого разделителя, что означает, что любое имя файла может быть обработано безопасно. Кроме того, использование двойных кавычек в "$file" и "${anarray[*]}" существенно.

* Многие считают, что инструменты GNU являются частью Bash, но технически это не так. Таким образом, stat и sort являются такими же неродными, как perl.

Ответ 2

Вы можете попробовать использовать команду stat с помощью sort:

stat -c '%Y %n' * | sort -t ' ' -nk1 | cut -d ' ' -f2-

Обновление:. Чтобы иметь дело с именем файла с символами новой строки, мы можем использовать формат %N в stat и вместо cut мы можем использовать awk следующим образом:

LANG=C stat -c '%Y^A%N' *| sort -t '^A' -nk1| awk -F '^A' '{print substr($2,2,length($2)-2)}'
  • Использование LANG=C необходимо, чтобы убедиться, что stat использует одинарные кавычки только в именах цитируемых файлов.
  • ^A - это символ conrtrol-A, введенный с помощью Control V A ключей вместе.

Ответ 3

При всех предостережениях и предупреждениях против ls для синтаксического анализа каталога все мы оказались в этой ситуации. Если вам нужно найти отсортированный вход в каталог, то о наиболее чистом использовании ls для подачи вашего цикла будет ls -opts | read -r name; do... Это будет обрабатывать пробелы в именах файлов и т.д., Не требуя reset of IFS из-за характер самой read. Пример:

ls -1rt | while read -r fname; do  # where '1' is ONE not little 'L'

Так что ищите более чистые решения, избегая ls, но если толчок наступает, то ls -opts можно использовать экономно без падения неба или драконов, выщипывающих ваши глаза.

позвольте мне добавить отказ от ответственности, чтобы все были счастливы. Если вам нравится newlines внутри ваших имен файлов, то не использовать ls для заполнения цикла. Если у вас нет newlines внутри ваших имен файлов, других нежелательных побочных эффектов нет.

Contra: TLDP Bash Howto Intro:

    #!/bin/bash
    for i in $( ls ); do
        echo item: $i
    done

Похоже, что пользователи SO не знают, что означает использование contra, - пожалуйста, просмотрите его перед downvoting.

Ответ 4

Как насчет решения с GNU find + sed + sort?

Пока в имени файла нет новых строк, это должно работать:

find . -type f -printf '%[email protected] %p\n' | sort -k 1nr | sed 's/^[^ ]* //'

Ответ 5

Каждый файл имеет три метки времени:

  • Время доступа: файл был открыт и прочитан. Также известен как atime.
  • Время модификации: файл был записан в. Также известен как mtime.
  • Время модификации Inode: статус файла был изменен, например, в файле была создана новая жесткая ссылка или удалена существующая; или если разрешения на файл были chmod-ed или несколько других вещей. Также известен как ctime.

Ни один из них не представляет время создания файла, эта информация не сохраняется нигде. Во время создания файла все три метки времени инициализируются, а затем каждый из них обновляется соответствующим образом, когда файл читается или записывается, или когда изменения в файлах являются chmoded или жесткая ссылка создана или уничтожена.

Таким образом, вы не можете перечислить файлы в соответствии с их временем создания файла, так как время создания файла нигде не сохраняется. Ближайшим совпадением будет время модификации inode.

См. описания параметров -t, -u, -c и -r на странице ls (1) man для получения дополнительной информации о том, как перечислять файлы в atime, mtime или ctime порядке.

Ответ 6

Это может быть немного больше, чтобы убедиться, что оно установлено (возможно, оно уже есть), но использование zsh вместо bash для этого script имеет большой смысл. Возможности глобирования имени файла намного богаче, но при этом используется язык sh.

files=( *(oc) )

создаст массив, чьи записи - все имена файлов в текущем каталоге, но отсортированные по времени изменения. (Используйте O вместо O, чтобы изменить порядок сортировки). Это будет включать каталоги, но вы можете ограничить соответствие обычным файлам (подобно предикату -type f до find):

files=( *(.oc) )

find требуется гораздо реже в сценариях zsh, так как большинство его применений покрываются различными флагами и квалификаторами glob.

Ответ 7

Я только что нашел способ сделать это с помощью bash и ls (GNU).
Предположим, вы хотите перебирать имена файлов, отсортированные по времени модификации (-t):

while read -r fname; do
    fname=${fname:1:((${#fname}-2))} # remove the leading and trailing "
    fname=${fname//\\\"/\"}          # removed the \ before any embedded "
    fname=$(echo -e "$fname")        # interpret the escaped characters
    file "$fname"                    # replace (YOU) `file` with anything
done < <(ls -At --quoting-style=c)

Объяснение

Учитывая некоторые имена файлов со специальными символами, это вывод ls:

$ ls -A
 filename with spaces   .hidden_filename  filename?with_a_tab  filename?with_a_newline  filename_"with_double_quotes"

$ ls -At --quoting-style=c
".hidden_filename"  " filename with spaces "  "filename_\"with_double_quotes\""  "filename\nwith_a_newline"  "filename\twith_a_tab"

Итак, вам нужно обработать немного каждого имени файла, чтобы получить фактический. Напоминая:

${fname:1:((${#fname}-2))} # remove the leading and trailing "
# ".hidden_filename" -> .hidden_filename
${fname//\\\"/\"}          # removed the \ before any embedded "
# filename_\"with_double_quotes\" -> filename_"with_double_quotes"
$(echo -e "$fname")        # interpret the escaped characters
# filename\twith_a_tab -> filename     with_a_tab

Пример

$ ./script.sh
.hidden_filename: empty
 filename with spaces : empty
filename_"with_double_quotes": empty
filename
with_a_newline: empty
filename    with_a_tab: empty

Как видно, file (или команда, которую вы хотите) хорошо интерпретирует каждое имя файла.

Ответ 8

Здесь используется способ stat с ассоциативным массивом.

n=0
declare -A arr
for file in *; do
    # modified=$(stat -f "%m" "$file") # For use with BSD/OS X
    modified=$(stat -c "%Y" "$file") # For use with GNU/Linux
    # Ensure stat timestamp is unique
    if [[ $modified == *"${!arr[@]}"* ]]; then
        modified=${modified}.$n
        ((n++))
    fi
    arr[$modified]="$file"
done
files=()
for index in $(IFS=$'\n'; echo "${!arr[*]}" | sort -n); do
    files+=("${arr[$index]}")
done

Так как sort сортирует строки, $(IFS=$'\n'; echo "${!arr[*]}" | sort -n) обеспечивает сортировку индексов ассоциативного массива путем установки разделителя полей в подоболочке на новую строку.

Цитирование в arr[$modified]="${file}" и files+=("${arr[$index]}") гарантирует, что имена файлов с такими оговорками, как новая строка, будут сохранены.