Mam kilka tysięcy plików w formacie nazwa_pliku.12345.end. Chcę zachować tylko co 12 plik, więc file.00012.end, file.00024.end ... file.99996.end i usuwam wszystko inne.
Pliki mogą także zawierać numery wcześniej w nazwie pliku i zwykle mają postać: file.00064.name.99999.end
Używam powłoki Bash i nie potrafię wymyślić, jak przesłonić pliki, a następnie uzyskać numer i sprawdzić, czy number%%12=0
usuwa plik, jeśli nie. Czy ktoś może mi pomóc?
Dziękuję, Dorina
Odpowiedzi:
Oto rozwiązanie Perla. Powinno to być znacznie szybsze dla tysięcy plików:
Które można dalej skondensować w:
Jeśli masz zbyt wiele plików i nie możesz użyć tego prostego
*
, możesz zrobić coś takiego:Jeśli chodzi o szybkość, oto porównanie tego podejścia i powłoki podanej w jednej z pozostałych odpowiedzi:
Jak widać, różnica jest ogromna, zgodnie z oczekiwaniami .
Wyjaśnienie
-e
Się po prostu powiedziećperl
, aby uruchomić skrypt podany w wierszu poleceń.@ARGV
to specjalna zmienna zawierająca wszystkie argumenty podane w skrypcie. Ponieważ dajemy go*
, będzie on zawierał wszystkie pliki (i katalogi) w bieżącym katalogu.grep
Będzie przeszukiwać listę nazw plików i patrzeć na te, które pasują ciąg cyfr, kropka iend
(/(\d+)\.end/)
.Ponieważ liczby (
\d
) znajdują się w grupie przechwytywania (nawiasy), są zapisywane jako$1
. Więcgrep
wtedy sprawdzić, czy liczba jest podzielna przez 12, a jeśli nie, to zostanie zwrócona nazwa pliku. Innymi słowy, tablica@bad
zawiera listę plików do usunięcia.Następnie przekazywana jest lista, do
unlink()
której usuwa pliki (ale nie katalogi).źródło
Biorąc pod uwagę, że twoje nazwy plików mają format
file.00064.name.99999.end
, najpierw musimy skrócić wszystko oprócz naszego numeru. W tym celu użyjemyfor
pętli.Musimy także powiedzieć powłoce Bash, aby użyła bazy 10, ponieważ arytmetyka Bash potraktuje ich liczby zaczynające się od 0 jako bazę 8, co zepsuje nam wszystko.
Jako skrypt uruchamiany w katalogu zawierającym pliki należy użyć:
Lub możesz użyć tego bardzo długiego brzydkiego polecenia, aby zrobić to samo:
Aby wyjaśnić wszystkie części:
for f in ./*
oznacza wszystko dla bieżącego katalogu, wykonaj .... Ustawia każdy znaleziony plik lub katalog jako zmienną $ f.if [[ -f "$f" ]]
sprawdza, czy znaleziony element jest plikiem, jeśli nie, przechodzimy doecho "$f is not...
części, co oznacza, że nie zaczynamy przypadkowo usuwać katalogów.file="${f%.*}"
ustawia zmienną $ file jako przycinanie nazw plików niezależnie od tego, co nastąpi po ostatnim.
.if [[ $((10#${file##*.} % 12)) -eq 0 ]]
jest miejscem, gdzie rozpoczyna się główna arytmetyka${file##*.}
Przycina wszystko przed ostatnim.
w naszej nazwie pliku bez rozszerzenia.$(( $num % $num2 ))
jest składnią arytmetyki Bash używającej operacji modulo,10#
na początku mówi Bashowi, aby używał podstawy 10, aby radzić sobie z tymi irytującymi wiodącymi zerami.$((10#${file##*.} % 12))
następnie pozostawia nam resztę liczby nazw plików podzieloną przez 12.-ne 0
sprawdza, czy reszta nie jest „równa” zero.rm
poleceniem, może chcesz zamienićrm
zeecho
podczas pierwszego uruchomienia to, aby sprawdzić, czy można uzyskać oczekiwane pliki do usunięcia.To rozwiązanie nie jest rekurencyjne, co oznacza, że będzie przetwarzać tylko pliki w bieżącym katalogu, nie będzie przechodzić do żadnych podkatalogów.
if
Sprawozdanie zecho
poleceniem, aby ostrzec o katalogach nie jest naprawdę koniecznerm
na swój własny będzie narzekać katalogów, a nie je usunąć, więc:Lub
Będzie również działać poprawnie.
źródło
rm
kilka tysięcy razy może być dość wolne. Proponujęecho
nazwę pliku zamiast rury i wyjście do pętlixargs rm
(opcje Dodaj jako potrzebne):for f in *; do if ... ; then echo "$f"; fi; done | xargs -rd '\n' -- rm --
.xargs
wersja zajęła 5 minut 1 sekundę. Czy może to być spowodowane narzutem naecho
@DavidFoerster?time { for f in *; do echo "$f"; done | xargs rm; }
porównaniu z 1m11.450s / 0m10.695s / 0m16.800s ztime { for f in *; do rm "$f"; done; }
na tmpfs. Bash to v4.3.11, jądro to v4.4.19.Możesz użyć rozszerzenia nawiasów Bash do generowania nazw zawierających co 12 cyfry. Utwórzmy dane testowe
Następnie możemy użyć następujących
Działa jednak beznadziejnie wolno w przypadku dużej liczby plików - generowanie tysięcy nazw zajmuje dużo czasu i pamięci - więc bardziej efektywne jest rozwiązanie.
źródło
Trochę długo, ale to właśnie przyszło mi do głowy.
Objaśnienie: Usuń co 12 plików jedenaście razy.
źródło
Z całą pokorą uważam, że to rozwiązanie jest o wiele ładniejsze niż inna odpowiedź:
Małe wyjaśnienie: Najpierw generujemy listę plików
find
. Otrzymujemy wszystkie pliki, których nazwa kończy się na.end
i których głębokość wynosi 1 (to znaczy są one bezpośrednio w katalogu roboczym, a nie w żadnych podfolderach. Możesz to pominąć, jeśli nie ma podfolderów). Lista wyników zostanie posortowana alfabetycznie.Następnie potokujemy tę listę do
awk
, w której używamy specjalnej zmiennej,NR
która jest numerem linii. Pomijamy każdy 12 plik, drukując pliki gdzieNR%12 != 0
.awk
Komenda może zostać skrócony doawk 'NR%12'
, ponieważ wynik operatora modulo zostanie zinterpretowane jako wartość logiczną i{print}
jest niejawnie zrobić tak.Mamy teraz listę plików, które należy usunąć, co możemy zrobić za pomocą xargs i rm.
xargs
uruchamia podaną komendę (rm
) ze standardowym wejściem jako argumentami.Jeśli masz wiele plików, pojawi się błąd, mówiąc coś w rodzaju „zbyt długiej listy argumentów” (na moim komputerze limit ten wynosi 256 kB, a minimalny wymagany przez POSIX to 4096 bajtów). Można tego uniknąć za pomocą
-n 100
flagi, która dzieli argumenty co 100 słów (nie wiersze, na co należy uważać, jeśli w nazwach plików są spacje) i wykonuje osobnerm
polecenie, każde z tylko 100 argumentami.źródło
-depth
musi być wcześniej-name
; ii) to się nie powiedzie, jeśli którakolwiek z nazw plików zawiera spacje; iii) zakładasz, że pliki zostaną wyświetlone w porządku rosnącym numerycznie (właśnieawk
to testujesz), ale prawie na pewno tak nie będzie. Dlatego spowoduje to usunięcie losowego zestawu plików.-depth
. Mimo to był to najmniejszy problem, najważniejszy z nich to to, że usuwasz losowy zestaw plików, a nie te, których chce OP.-depth
nie bierze wartości i robi coś przeciwnego do tego, co myślisz. Patrzman find
: „-depth Przetwarzaj zawartość każdego katalogu przed samym katalogiem.”. Więc to faktycznie spadnie do podkatalogów i spowoduje spustoszenie w całym miejscu.-depth n
i-maxdepth n
istnieją. Pierwsza wymaga głębokości dokładnie n, a druga może wynosić <= n. II). Tak, to źle, ale w tym konkretnym przykładzie nie ma to znaczenia. Możesz to naprawić za pomocąfind ... -print0 | awk 'BEGIN {RS="\0"}; NR%12 != 0' | xargs -0 -n100 rm
, który używa bajtu zerowego jako separatora rekordów (co nie jest dozwolone w nazwach plików). III) Ponownie, w tym przypadku założenie jest uzasadnione. W przeciwnym razie możesz wstawićsort -n
pomiędzyfind
iawk
lub przekierowaćfind
do pliku i posortować go w dowolny sposób.find
. Ponownie jednak głównym problemem jest to, że zakładasz, żefind
zwraca posortowaną listę. Tak nie jest.Aby użyć tylko bash, moim pierwszym podejściem byłoby: 1. przenieść wszystkie pliki, które chcesz zachować, do innego katalogu (tj. Wszystkie, których liczba w nazwie pliku jest wielokrotnością 12), a następnie 2. usunąć wszystkie pozostałe pliki w katalogu, następnie 3. umieść wiele z 12 plików, które zachowałeś tam, gdzie były. Więc coś takiego może działać:
źródło
filename
część, jeśli nie jest spójna?