Итерации по списку имен файлов, чтобы они были созданы в 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]}")
гарантирует, что имена файлов с такими оговорками, как новая строка, будут сохранены.