Функція Bash, щоб знайти новітній шаблон відповідності файлів


141

У Bash я хотів би створити функцію, яка повертає ім'я найновішого файлу, який відповідає певній схемі. Наприклад, у мене є каталог файлів, таких як:

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

Я хочу, щоб найновіший файл, який починається з 'b2'. Як це зробити в баш? Мені це потрібно мати у своєму ~/.bash_profileсценарії.


4
див. superuser.com/questions/294161/… для отримання додаткових підказок. Сортування - це ключовий крок, щоб отримати найновіший файл
Вольфганг Фаль

Відповіді:


229

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

8
Зауважте іншим: якщо ви робите це для каталогу, ви додасте параметр -d до ls, як-от 'ls -td <pattern> | голова -1 '
ken.ganong

5
Розборі LS посилання говорить не робити цього , і рекомендує методи в BashFAQ 99 . Я шукаю 1-вкладиш, а не щось, що має бути захищеним від кулі, щоб включити його в сценарій, тож я продовжую розбирати так небезпечно, як @lesmana.
однойменний

1
@Eponymous: Якщо ви шукаєте один вкладиш, не використовуючи крихкий ls, printf "%s\n" b2* | head -1зробимо це за вас.
Девід Онгаро

2
@DavidOngaro У запитанні не сказано, що імена файлів - це номери версій. Йдеться про час модифікації. Навіть якщо припущення про ім'я файлу b2.10_5_2вбиває це рішення.
однойменний

1
Ваш один лайнер дає мені правильну відповідь, але "правильний" спосіб насправді дає мені найдавніший файл. Будь-яка ідея чому?
NewNameStat

15

Поєднання 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: `...'
Яакко

Ой! ...означає "більше параметрів". Просто опустіть його, якщо він вам не потрібен.
Борис Бродський

2
Я виявив, що це може повернути файл, який не відповідає шаблону, якщо немає файлів, які відповідають шаблону. Це трапляється через те, що пошук нічого не передає xargs, який потім викликає ls без списків файлів, внаслідок чого він працює на всіх файлах. Рішення полягає в тому, щоб додати -rдо командного рядка xargs, який повідомляє xargs не запускати його командний рядок, якщо він нічого не отримує на своєму стандартному вході.
starfry

@starfry дякую! Гарний улов. Я додав -rу відповідь.
Борис Бродський

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 і повинен обробляти файли, імена яких містять нові рядки або інші незвичайні символи.


1
Ви можете скористатися, nullglob_shopt=$(shopt -p nullglob)а потім пізніше, $nullglobщоб повернути, nullglobяк це було раніше.
gniourf_gniourf

Пропозиція @gniourf_gniourf використовувати $ (shopt -p nullglob) - це хороша пропозиція. Я, як правило, намагаюся уникати використання підстановки команд ( $()або зворотних посилань), тому що це повільно, особливо під Cygwin, навіть коли команда використовує лише вбудовані. Крім того, контекст підрозділу, в якому команди запускаються, іноді може змусити їх вести себе несподівано. Я також намагаюся уникати зберігання команд у змінних (наприклад nullglob_shopt), тому що дуже погані речі можуть трапитися, якщо ви неправильно отримаєте значення змінної.
pjh

Я вдячний увагою до деталей, які можуть призвести до незрозумілого невдачі, якщо її не помітити. Дякую!
Рон Берк

Мені подобається, що ти пішов на більш унікальний шлях вирішення проблеми! Безперечно, що в Unix / Linux існує більше ніж один спосіб «зняти cat!». Навіть якщо для цього потрібно більше роботи, це має перевагу показувати людям поняття. Майте +1!
Прифтан

3

Незвичайні назви файлів (наприклад, файл, що містить дійсний \nсимвол, може спричинити хаос при такому типі розбору. Ось спосіб це зробити в Perl:

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

Це трансформація Шварца, яка використовується там.


1
Нехай шварц буде з вами!
Натан Монтелеоне

ця відповідь може спрацювати, але я б не повірив їй, враховуючи погану документацію.
Вольфганг Фаль

1

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

$ stat -f "%m%t%N" b2* | sort -rn | head -1 | cut -f2-

ніпе. "stat: не вдається прочитати інформацію файлової системи для '% m% t% N': Немає такого файлу чи каталогу"
Ken Ingram

Я думаю, це може бути для версії Mac / FreeBSD stat, якщо я правильно запам’ятав його параметри. Щоб отримати подібний вихід на інших платформах, ви можете скористатисяstat -c $'%Y\t%n' b2* | sort -rn | head -n1 | cut -f2-
Jeffrey Cash

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

Який є:

Ім'я файлу з найстарішою зміненою часовою позначкою файлу під заданим каталогом, що відповідає заданому шаблону.


1

Використовуйте команду find.

Припустимо, що ви використовуєте 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


працювати з іменами файлів, що містять пробіли, змінити cut -d '' -f2,3,4,5,6,7,8,9 ...
valodzka

0

Існує набагато ефективніший спосіб досягти цього. Розглянемо таку команду:

find . -cmin 1 -name "b2*"

Ця команда знаходить останній файл, створений рівно хвилину тому при пошуку підстановки на "b2 *". Якщо ви хочете, щоб файли за останні два дні, то вам буде краще скористатися командою нижче:

find . -mtime 2 -name "b2*"

"". представляє поточний каталог. Сподіваюся, це допомагає.


9
Насправді це не "найновіший шаблон узгодження файлів" ... він просто знайде всі файли, що відповідають шаблону, створені хвилину тому або змінені два дні тому.
GnP

Ця відповідь ґрунтувалася на поставленому питанні. Крім того, ви можете налаштувати команду, щоб переглянути останній файл, який з’явився за день або близько того. Це залежить від того, що ви намагаєтеся зробити.
Науфф

"налаштування" - це не відповідь. це як опублікувати це як відповідь: "Просто налаштуйте команду find і знайдіть відповідь залежно від того, що ви хочете зробити".
Кеннет Селесте

Не впевнений у непотрібному коментарі. Якщо ви вважаєте, що моя відповідь не обґрунтовує, то, будь ласка, вкажіть належну причину, чому моя відповідь не має сенсу в ПРИКЛАДАХ. Якщо ви не можете цього зробити, то не пишіть коментарів далі.
Науфал

1
У вашому рішенні потрібно знати, коли було створено останній файл. Це було не в питанні, так ні, ваша відповідь не ґрунтується на поставленому питанні.
паб
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.