Funkcja Bash, aby znaleźć najnowszy wzorzec pasujący do pliku

141

W Bash chciałbym stworzyć funkcję, która zwraca nazwę najnowszego pliku, który pasuje do określonego wzorca. Na przykład mam katalog plików takich jak:

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

Chcę najnowszy plik zaczynający się od „b2”. Jak mam to zrobić w bash? Muszę to mieć w swoim ~/.bash_profileskrypcie.

jlconlin
źródło
4
zobacz superuser.com/questions/294161/…, aby uzyskać więcej wskazówek dotyczących odpowiedzi. Sortowanie jest kluczowym krokiem do uzyskania najnowszego pliku
Wolfgang Fahl

Odpowiedzi:

229

lsKomenda ma parametr -tsortowania przez czas. Następnie możesz pobrać pierwszy (najnowszy) z head -1.

ls -t b2* | head -1

Ale uważaj: dlaczego nie powinieneś analizować wyjścia ls

Moja osobista opinia: parsowanie lsjest niebezpieczne tylko wtedy, gdy nazwy plików mogą zawierać zabawne znaki, takie jak spacje lub znaki nowej linii. Jeśli możesz zagwarantować, że nazwy plików nie będą zawierały zabawnych znaków, parsowanie lsjest całkiem bezpieczne.

Jeśli tworzysz skrypt, który ma być uruchamiany przez wiele osób na wielu systemach w różnych sytuacjach, to bardzo polecam, aby go nie analizować ls.

Oto jak to zrobić "dobrze": Jak mogę znaleźć najnowszy (najnowszy, najwcześniejszy, najstarszy) plik w katalogu?

unset -v latest
for file in "$dir"/*; do
  [[ $file -nt $latest ]] && latest=$file
done
lesmana
źródło
8
Uwaga dla innych: jeśli robisz to dla katalogu, powinieneś dodać opcję -d do ls, na przykład 'ls -td <wzór> | głowa -1 '
ken.ganong
5
Parsowanie LS Link nie mówi, aby to zrobić i zaleca metody w BashFAQ 99 . Szukam raczej 1-liniowego tekstu niż czegoś kuloodpornego do umieszczenia w skrypcie, więc nadal będę analizować ls w sposób niebezpieczny, jak @lesmana.
tytułowy
1
@Eponymous: Jeśli szukasz jednej wkładki bez użycia delikatnej ls, printf "%s\n" b2* | head -1zrobię to za Ciebie.
David Ongaro
2
@DavidOngaro Pytanie nie mówi, że nazwy plików są numerami wersji. Chodzi o czasy modyfikacji. Nawet przy założeniu nazwy pliku b2.10_5_2zabija to rozwiązanie.
Tytułowy
1
Twoja jedna linijka daje mi właściwą odpowiedź, ale „właściwy” sposób to w rzeczywistości podanie najstarszego pliku. Każdy pomysł, dlaczego?
NewNameStat
15

Połączenie findi lsdziała dobrze w przypadku

  • nazwy plików bez nowych linii
  • niezbyt duża ilość plików
  • niezbyt długie nazwy plików

Rozwiązanie:

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

Rozbijmy to:

Dzięki temu findmożemy dopasować wszystkie interesujące pliki, takie jak:

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

następnie używając -print0możemy bezpiecznie przekazać wszystkie nazwy plików do lstego:

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

findmożna tutaj dodać dodatkowe parametry wyszukiwania i wzorce

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

ls -tposortuje pliki według czasu modyfikacji (od najnowszych) i wydrukuje je jeden w wierszu. Możesz użyć -cdo sortowania według czasu utworzenia. Uwaga : to zepsuje się w przypadku nazw plików zawierających znaki nowej linii.

W końcu head -1dostajemy pierwszy plik na posortowanej liście.

Uwaga: xargs użyj ograniczeń systemowych do rozmiaru listy argumentów. Jeśli ten rozmiar przekroczy, xargszadzwoni lswiele razy. Spowoduje to przerwanie sortowania i prawdopodobnie również ostateczny wynik. Biegać

xargs  --show-limits

aby sprawdzić limity w Twoim systemie.

Uwaga 2: użyj, find . -maxdepth 1 -name "my-pattern" -print0jeśli nie chcesz przeszukiwać plików przez podfoldery.

Uwaga 3: Jak wskazał @starfry - -rargument za xargsuniemożliwia wywołanie programu ls -1 -t, jeśli żaden plik nie został dopasowany przez rozszerzenie find. Dziękuję za sugestię.

Boris Brodski
źródło
2
Jest to lepsze niż rozwiązania oparte na ls, ponieważ działa dla katalogów z bardzo dużą liczbą plików, w których ls się dławi.
Marcin Żukowski
find . -name "my-pattern" ... -print0daje mifind: paths must precede expression: `...'
Jaakko,
O! ...oznacza „więcej parametrów”. Po prostu go pomiń, jeśli go nie potrzebujesz.
Boris Brodski
2
Zauważyłem, że może to zwrócić plik, który nie pasuje do wzorca, jeśli nie ma plików pasujących do wzorca. Dzieje się tak, ponieważ find nie przekazuje niczego do xargs, który następnie wywołuje ls bez list plików, powodując, że działa na wszystkich plikach. Rozwiązaniem jest dodanie -rdo wiersza poleceń xargs, który mówi xargs, aby nie uruchamiał swojego wiersza poleceń, jeśli nic nie otrzyma ze swojego standardowego wejścia.
starfry
@starfry dziękuję! Dobry chwyt. Dodałem -rdo odpowiedzi.
Boris Brodski
7

Oto możliwa implementacja wymaganej funkcji 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
}

Używa tylko wbudowanych Bash i powinien obsługiwać pliki, których nazwy zawierają znaki nowej linii lub inne nietypowe znaki.

pjh
źródło
1
Możesz użyć, nullglob_shopt=$(shopt -p nullglob)a później $nullglobprzywrócić nullglobto, jak było wcześniej.
gniourf_gniourf
Sugestia @gniourf_gniourf aby użyć $ (shopt -p nullglob) jest dobra. Generalnie staram się unikać podstawiania poleceń ( $()lub znaków odwrotnych), ponieważ jest powolny, szczególnie pod Cygwin, nawet jeśli polecenie używa tylko poleceń wbudowanych. Ponadto kontekst podpowłoki, w którym polecenia są uruchamiane, może czasami powodować, że zachowują się one w nieoczekiwany sposób. Staram się również unikać przechowywania poleceń w zmiennych (takich jak nullglob_shopt), ponieważ mogą się zdarzyć bardzo złe rzeczy, jeśli źle otrzymasz wartość zmiennej.
pjh
Doceniam dbałość o szczegóły, które mogą prowadzić do niejasnych błędów, gdy zostaną przeoczone. Dzięki!
Ron Burk
Podoba mi się, że wybrałeś bardziej unikalny sposób rozwiązania problemu! To pewne, że w systemie Unix / Linux jest więcej niż jeden sposób na „skin cat!”. Nawet jeśli wymaga to więcej pracy, ma tę zaletę, że pokazuje ludziom koncepcje. Zdobądź +1!
Pryftan
3

Nietypowe nazwy plików (na przykład plik zawierający prawidłowy \nznak mogą siać spustoszenie przy tego rodzaju analizowaniu. Oto sposób, aby to zrobić w Perlu:

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

To jest zastosowana tam transformacja Schwartza .

glenn jackman
źródło
1
Niech schwartz będzie z tobą!
Nathan Monteleone
ta odpowiedź może działać, ale nie ufałbym jej, biorąc pod uwagę kiepską dokumentację.
Wolfgang Fahl
1

Możesz użyć statz plikiem glob i dekoracją-sortowaniem-undecorate z czasem pliku dodanym z przodu:

$ stat -f "%m%t%N" b2* | sort -rn | head -1 | cut -f2-
psie
źródło
nie. „stat: nie można odczytać informacji o systemie plików dla„% m% t% N ”: nie ma takiego pliku lub katalogu”
Ken Ingram
Myślę, że to może być dla wersji Mac / FreeBSD programu stat, jeśli dobrze pamiętam jego opcje. Aby uzyskać podobne wyniki na innych platformach, możesz użyćstat -c $'%Y\t%n' b2* | sort -rn | head -n1 | cut -f2-
Jeffrey Cash
1

Inkantacja funkcji czarnej magii dla tych, którzy chcą find ... xargs ... head ...powyższego rozwiązania, ale w łatwej w użyciu formie funkcji, więc nie musisz myśleć:

#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

Wydruki:

file2.txt

Który jest:

Nazwa pliku z najstarszym zmodyfikowanym znacznikiem czasu pliku w podanym katalogu pasującym do podanego wzorca.

Eric Leschinski
źródło
1

Użyj polecenia znajdź.

Zakładając, że używasz Bash 4.2+, użyj -printf '%T+ %p\n'jako wartości znacznika czasu pliku.

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

Przykład:

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

Aby uzyskać bardziej przydatny skrypt, zobacz najnowszy skrypt znajdujący się tutaj: https://github.com/l3x/helpers

l3x
źródło
pracować z nazwami plików, które zawierają spacje zmień cut -d '' -f2,3,4,5,6,7,8,9 ...
valodzka
0

Jest o wiele skuteczniejszy sposób osiągnięcia tego. Rozważ następujące polecenie:

find . -cmin 1 -name "b2*"

To polecenie znajduje najnowszy plik utworzony dokładnie minutę temu przy wyszukiwaniu symboli wieloznacznych na „b2 *”. Jeśli chcesz pliki z ostatnich dwóch dni, lepiej będzie, używając poniższego polecenia:

find . -mtime 2 -name "b2*"

„.” reprezentuje bieżący katalog. Mam nadzieję że to pomoże.

Naufal
źródło
9
To w rzeczywistości nie znajduje „najnowszego wzorca dopasowania pliku” ... po prostu znajduje wszystkie pliki pasujące do wzorca utworzonego minutę temu lub zmodyfikowanego dwa dni temu.
GnP
Ta odpowiedź była oparta na zadanym pytaniu. Możesz także dostosować polecenie, aby spojrzeć na najnowszy plik, który pojawił się dzień lub dwa dni temu. To zależy od tego, co próbujesz zrobić.
Naufal
„podkręcanie” nie jest odpowiedzią. to tak, jakby zamieścić to jako odpowiedź: „Po prostu dostosuj polecenie wyszukiwania i znajdź odpowiedź w zależności od tego, co chcesz zrobić”.
Kennet Celeste
Nie jestem pewien co do niepotrzebnego komentarza. Jeśli uważasz, że moja odpowiedź nie jest uzasadniająca, to proszę podać właściwy powód, dla którego moja odpowiedź nie ma sensu w PRZYKŁADACH. Jeśli nie możesz tego zrobić, powstrzymaj się od dalszych komentarzy.
Naufal
1
Twoje rozwiązanie wymaga, abyś wiedział, kiedy utworzono najnowszy plik. Tego nie było w pytaniu, więc nie, twoja odpowiedź nie jest oparta na zadanym pytaniu.
Bloke Down The Pub