Функция Bash для поиска новейшего шаблона соответствия файла

146

В Bash я хотел бы создать функцию, которая возвращает имя самого нового файла, соответствующего определенному шаблону. Например, у меня есть каталог таких файлов, как:

Directory/
   a1.1_5_1
   a1.2_1_4
   b2.1_0
   b2.2_3_4
   b2.3_2_0

Мне нужен самый новый файл, который начинается с "b2". Как мне это сделать в bash? Мне нужно, чтобы это было в моем ~/.bash_profileсценарии.

Jlconlin
источник
4
см. superuser.com/questions/294161/… для получения дополнительных советов по ответам. Сортировка - ключевой шаг для получения самого нового файла
Вольфганг Фаль

Ответы:

237

У lsкоманды есть параметр -tдля сортировки по времени. Затем вы можете получить первый (самый новый) с помощью head -1.

ls -t b2* | head -1

Но будьте осторожны: почему вы не должны анализировать вывод ls

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

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

Вот как это сделать «правильно»: Как мне найти последний (самый новый, самый ранний, самый старый) файл в каталоге?

unset -v latest
for file in "$dir"/*; do
  [[ $file -nt $latest ]] && latest=$file
done
лесмана
источник
9
Примечание для других: если вы делаете это для каталога, вы должны добавить параметр -d к ls, например, ls -td <pattern> | head -1 '
ken.ganong
5
Разборе LS ссылка говорит не делать этого , и рекомендует методы в BashFAQ 99 . Я ищу однострочник, а не что-то пуленепробиваемое для включения в скрипт, поэтому я буду продолжать небезопасно разбирать ls, как @lesmana.
Одноименный
1
@Eponymous: Если вы ищете одинарный лайнер без хрупкого ls, printf "%s\n" b2* | head -1сделаю это за вас.
Дэвид Онгаро
2
@DavidOngaro Вопрос не говорит, что имена файлов являются номерами версий. Речь идет о времени модификации. Даже с предположением имени файла b2.10_5_2убивает это решение.
Одноименный,
1
Ваш лайнер дает мне правильный ответ, но «правильный» способ на самом деле дает мне самый старый файл. Есть идеи, почему?
NewNameStat
17

Комбинация findи lsподходит для

  • имена файлов без символов новой строки
  • не очень большое количество файлов
  • не очень длинные имена файлов

Решение:

find . -name "my-pattern" -print0 |
    xargs -r -0 ls -1 -t |
    head -1

Давайте разберемся:

С помощью findмы можем сопоставить все интересные файлы следующим образом:

find . -name "my-pattern" ...

затем с помощью -print0мы можем безопасно передать все имена файлов lsследующим образом:

find . -name "my-pattern" -print0 | xargs -r -0 ls -1 -t

findздесь можно добавить дополнительные параметры поиска и шаблоны

find . -name "my-pattern" ... -print0 | xargs -r -0 ls -1 -t

ls -tотсортирует файлы по времени модификации (сначала самые новые) и распечатает их по одному. Вы можете использовать -cдля сортировки по времени создания. Примечание : это нарушит имена файлов, содержащие символы новой строки.

Наконец, head -1мы получаем первый файл в отсортированном списке.

Примечание: xargs используйте системные ограничения на размер списка аргументов. Если этот размер превышает, xargsвызовет lsнесколько раз. Это нарушит сортировку и, возможно, также окончательный результат. Бегать

xargs  --show-limits

чтобы проверить лимиты в вашей системе.

Примечание 2: используйте, find . -maxdepth 1 -name "my-pattern" -print0если вы не хотите искать файлы во вложенных папках.

Примечание 3: как указано в @starfry - -rаргумент для xargsпредотвращает вызов ls -1 -t, если файлы не были сопоставлены find. Спасибо за предложение.

Борис Бродский
источник
2
Это лучше, чем решения на основе ls, поскольку он работает для каталогов с очень большим количеством файлов, где ls задыхается.
Марцин Жуковски
find . -name "my-pattern" ... -print0дает мнеfind: paths must precede expression: `...'
Jaakko
Ой! ...означает «больше параметров». Просто опустите его, если он вам не нужен.
Борис Бродский 06
3
Я обнаружил, что это может вернуть файл, который не соответствует шаблону, если нет файлов, соответствующих шаблону. Это происходит потому, что find ничего не передает в xargs, который затем вызывает ls без списков файлов, заставляя его работать со всеми файлами. Решение состоит в том, чтобы добавить -rв командную строку xargs, которая сообщает xargs не запускать свою командную строку, если она ничего не получает на свой стандартный ввод.
starfry 01
@starfry, спасибо! Хорошо поймал. Я добавил -rк ответу.
Борис Бродский 06
7

Это возможная реализация требуемой функции Bash:

# Print the newest file, if any, matching the given pattern
# Example usage:
#   newest_matching_file 'b2*'
# WARNING: Files whose names begin with a dot will not be checked
function newest_matching_file
{
    # Use ${1-} instead of $1 in case 'nounset' is set
    local -r glob_pattern=${1-}

    if (( $# != 1 )) ; then
        echo 'usage: newest_matching_file GLOB_PATTERN' >&2
        return 1
    fi

    # To avoid printing garbage if no files match the pattern, set
    # 'nullglob' if necessary
    local -i need_to_unset_nullglob=0
    if [[ ":$BASHOPTS:" != *:nullglob:* ]] ; then
        shopt -s nullglob
        need_to_unset_nullglob=1
    fi

    newest_file=
    for file in $glob_pattern ; do
        [[ -z $newest_file || $file -nt $newest_file ]] \
            && newest_file=$file
    done

    # To avoid unexpected behaviour elsewhere, unset nullglob if it was
    # set by this function
    (( need_to_unset_nullglob )) && shopt -u nullglob

    # Use printf instead of echo in case the file name begins with '-'
    [[ -n $newest_file ]] && printf '%s\n' "$newest_file"

    return 0
}

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

pjh
источник
1
Вы могли бы использовать, nullglob_shopt=$(shopt -p nullglob)а потом потом $nullglobвернуть, nullglobкак было раньше.
gniourf_gniourf
Предложение @gniourf_gniourf использовать $ (shopt -p nullglob) является хорошим. Обычно я стараюсь избегать подстановки команд ( $()или обратных кавычек), потому что это медленно, особенно в Cygwin, даже когда команда использует только встроенные команды. Кроме того, контекст подоболочки, в котором запускаются команды, может иногда вызывать их неожиданное поведение. Я также стараюсь избегать хранения команд в переменных (например, nullglob_shopt), потому что могут произойти очень плохие вещи, если вы ошибетесь в значении переменной.
pjh 06
Я ценю внимание к деталям, игнорирование которых может привести к неясным неудачам. Благодарность!
Ron Burk
Мне нравится, что вы выбрали более уникальный способ решения проблемы! Несомненно, что в Unix / Linux существует более одного способа «обшить cat!». Даже если это требует больше работы, это помогает показать людям концепции. Получите +1!
Pryftan
4

Необычные имена файлов (например, файл, содержащий допустимый \nсимвол, могут нанести ущерб подобному синтаксическому анализу. Вот способ сделать это в Perl:

perl -le '@sorted = map {$_->[0]} 
                    sort {$a->[1] <=> $b->[1]} 
                    map {[$_, -M $_]} 
                    @ARGV;
          print $sorted[0]
' b2*

Здесь использовано преобразование Шварца .

Гленн Джекман
источник
1
Да пребудет с вами шварц!
Натан Монтелеоне
этот ответ может сработать, но я бы не стал ему доверять, учитывая плохую документацию.
Вольфганг Фаль
2

Используйте команду поиска.

Предполагая, что вы используете Bash 4.2+, используйте -printf '%T+ %p\n'для значения отметки времени файла.

find $DIR -type f -printf '%T+ %p\n' | sort -r | head -n 1 | cut -d' ' -f2

Пример:

find ~/Downloads -type f -printf '%T+ %p\n' | sort -r | head -n 1 | cut -d' ' -f2

Более полезный сценарий можно найти здесь: https://github.com/l3x/helpers.

l3x
источник
для работы с именами файлов, содержащими пробелы, измените cut -d '' -f2,3,4,5,6,7,8,9 ...
valodzka
1

Вы можете использовать statс файловым глобусом и decorate-sort-undecorate с указанием времени файла спереди:

$ stat -f "%m%t%N" b2* | sort -rn | head -1 | cut -f2-
чувак
источник
Неа. «stat: невозможно прочитать информацию о файловой системе для '% m% t% N': нет такого файла или каталога»
Кен Ингрэм
Я думаю, что это может быть для версии Mac / FreeBSD stat, если я правильно помню ее параметры. Чтобы получить аналогичный результат на других платформах, вы можете использоватьstat -c $'%Y\t%n' b2* | sort -rn | head -n1 | cut -f2-
Джеффри Кэш
1

Заклинание функции темной магии для тех, кто хочет find ... xargs ... head ...решение, описанное выше, но в простой в использовании функциональной форме, поэтому вам не нужно думать:

#define the function
find_newest_file_matching_pattern_under_directory(){
    echo $(find $1 -name $2 -print0 | xargs -0 ls -1 -t | head -1)
}

#setup:
#mkdir /tmp/files_to_move
#cd /tmp/files_to_move
#touch file1.txt
#touch file2.txt

#invoke the function:
newest_file=$( find_newest_file_matching_pattern_under_directory /tmp/files_to_move/ bc* )
echo $newest_file

Печать:

file2.txt

Который:

Имя файла с самой старой измененной меткой времени файла в заданном каталоге, соответствующем заданному шаблону.

Эрик Лещински
источник
0

Есть гораздо более эффективный способ добиться этого. Рассмотрим следующую команду:

find . -cmin 1 -name "b2*"

Эта команда находит последний файл, созданный ровно минуту назад, с поиском по шаблону «b2 *». Если вам нужны файлы за последние два дня, вам лучше использовать следующую команду:

find . -mtime 2 -name "b2*"

Знак "." представляет текущий каталог. Надеюсь это поможет.

Науфаль
источник
9
На самом деле он не находит «новейший шаблон сопоставления файлов» ... он просто находит все файлы, соответствующие шаблону, созданные минуту назад или измененные два дня назад.
GnP
Этот ответ был основан на заданном вопросе. Кроме того, вы можете настроить команду так, чтобы она просматривала последний файл, полученный примерно день назад. Это зависит от того, что вы пытаетесь сделать.
Naufal
«настройка» - это не ответ. это как опубликовать это как ответ: «Просто настройте команду find и найдите ответ в зависимости от того, что вы хотите сделать».
Kennet Celeste
Не уверен в ненужном комментарии. Если вы считаете, что мой ответ не обоснован, просьба указать причину, почему мой ответ не имеет смысла с ПРИМЕРАМИ. Если это невозможно, пожалуйста, воздержитесь от дальнейших комментариев.
Naufal
1
Ваше решение требует, чтобы вы знали, когда был создан последний файл. Этого не было в вопросе, поэтому нет, ваш ответ не основан на заданном вопросе.
Bloke Down The Pub