Znajdź katalogi, które NIE zawierają pliku

58

Tak, uporządkuję swoją muzykę. Wszystko ułożyłem pięknie w następującej mantrze: /Artist/Album/Track - Artist - Title.exta jeśli taka istnieje, pokrywa pozostaje w środku /Artist/Album/cover.(jpg|png).

Chcę przejrzeć wszystkie katalogi drugiego poziomu i znaleźć te, które nie mają okładki. Na drugim poziomie mam na myśli, że nie obchodzi mnie, czy /Britney Spears/nie ma cover.jpg, ale byłbym zainteresowany, gdyby /Britney Spears/In The Zone/go nie miał.

Nie przejmuj się pobieraniem okładek (jutro to dla mnie fajny projekt). Dbam tylko o chwalebną bzdurę o odwrotnym findprzykładzie.

Oli
źródło
dla każdego, kto jest zainteresowany pobraniem brakujących okładek, po prostu zainstaluj launchpad.net/coverlovin i zamień -print w odpowiedzi @phoibos na „-exec ./coverlovin.py {} \;”
Dror Cohen

Odpowiedzi:

81

Przypadek 1: Znasz dokładną nazwę pliku do wyszukania

Użyj za findpomocą, test -e your_fileaby sprawdzić, czy plik istnieje. Na przykład szukasz katalogów, w których nie ma żadnych cover.jpg:

find base_dir -mindepth 2 -maxdepth 2 -type d '!' -exec test -e "{}/cover.jpg" ';' -print

Rozróżnia małe i wielkie litery.

Przypadek 2: Chcesz być bardziej elastyczny

Nie jesteś pewien sprawy, a rozszerzenie może być jPg, png...

find base_dir -mindepth 2 -maxdepth 2 -type d '!' -exec sh -c 'ls -1 "{}"|egrep -i -q "^cover\.(jpg|png)$"' ';' -print

Wyjaśnienie:

  • Musisz spawnować powłokę shdla każdego katalogu, ponieważ podczas korzystania z niego nie jest możliwe przesyłanie potokówfind
  • ls -1 "{}"wypisuje tylko nazwy plików katalogu, który findaktualnie przechodzi
  • egrep(zamiast grep) używa rozszerzonych wyrażeń regularnych; -isprawia, że ​​wyszukiwanie nie rozróżnia wielkości liter, -qsprawia, że ​​pomija jakiekolwiek dane wyjściowe
  • "^cover\.(jpg|png)$"jest wzorcem wyszukiwania. W tym przykładzie, pasuje np cOver.png, Cover.JPGalbo cover.png. .Musi być uciekł w przeciwnym razie oznacza to, że pasuje dowolny znak. ^oznacza początek linii, $jej koniec

Inne przykłady wzorca wyszukiwania dla egrep :

Zamień egrep -i -q "^cover\.(jpg|png)$"część na:

  • egrep -i -q "cover\.(jpg|png)$": Pasuje także cd_cover.png, album_cover.JPG...
  • egrep -q "^cover\.(jpg|png)$": Dopasowuje cover.png, cover.jpgale NIE Cover.jpg(rozróżnianie wielkości liter nie jest wyłączone)
  • egrep -iq "^(cover|front)\.jpg$": Pasuje np front.jpg, Cover.JPGale nie Cover.PNG

Aby uzyskać więcej informacji na ten temat, sprawdź Wyrażenia regularne .

phoibos
źródło
Absolutnie piękny - z tym problemem, że nie można elastycznie wybierać między przypadkami lub różnymi rozszerzeniami (próbowałem z wieloznacznikiem, ale nie można). Zastanawiam się, czy istnieje lepsza alternatywa dla test.
Oli
1
Hmm, możesz w ten sposób zagnieździć znalezisko, -exec bash -c '[[ -n $(find "{}" -iname "cover.*") ]]' \;ale jest to dość brudne pod względem optymalizacji. Ale to działa.
Oli
Zauważyłem, że możesz przekazać testładunek -o EXPRESSIONzapytań OR ... np.: test -e "{}/cover.jpg" -o -e "{}/cover.png"Co jest lepsze niż przeprowadzanie pełnego wyszukiwania, ale nadal rozróżnia małe i wielkie litery.
Oli
Powinienem zauważyć, że porównanie wydajności tego (dwa testy, według mojego ostatniego komentarza) z pozostałymi dwoma rozwiązaniami (komunikowanie i wyszukiwanie globowania) jest zdecydowanie najwolniejsze (odpowiednio 684 ms w porównaniu do 40 ms i 50 ms)
Oli
Oryginalne rozwiązanie odpowiedzi trwa dłużej niż sekundę i psuje się w okolicznościach, które mają $nazwę dir (na przykład Ke $ ha).
Oli
12

Proste, okazuje się. Poniżej przedstawiono listę katalogów z okładką i porównuje ją z listą wszystkich katalogów drugiego poziomu. Linie pojawiające się w obu „plikach” są pomijane, pozostawiając listę katalogów, które wymagają okładek.

comm -3 \
    <(find ~/Music/ -iname 'cover.*' -printf '%h\n' | sort -u) \
    <(find ~/Music/ -maxdepth 2 -mindepth 2 -type d | sort) \
| sed 's/^.*Music\///'

Brawo.

Uwagi:

  • commArgumenty są następujące:

    • -1 pomija wiersze unikalne dla pliku 1
    • -2 pomija wiersze unikalne dla file2
    • -3 pomija linie pojawiające się w obu plikach
  • commpobiera tylko pliki, stąd kooky <(...)metoda wprowadzania. Spowoduje to przesłanie zawartości przez prawdziwy [tymczasowy] plik.

  • commwymaga posortowanych danych wejściowych lub nie działa i findw żadnym wypadku nie gwarantuje zamówienia. Musi także być wyjątkowy. Pierwsza findoperacja może znaleźć wiele plików, cover.*więc mogą istnieć zduplikowane wpisy. sort -uszybko potrząsa nimi do jednego. Drugie znalezisko zawsze będzie wyjątkowe.

  • dirnamejest przydatnym narzędziem do uzyskania katalogu pliku bez uciekania się do sed(i in.).

  • findi commoba są nieco niechlujne z ich wynikami. Ostatecznym sedzadaniem jest posprzątanie rzeczy, więc nie masz nic Artist/Album. To może być lub może nie być dla Ciebie pożądane.

Oli
źródło
2
Twój pierwszy findmożna uprościć find ~/Music/ -iname 'cover.*' -printf '%h\n', unikając takiej potrzeby dirname. chociaż dirnamejest przydatny gdzie indziej.
Tom
Dzięki @Tom, to jest o wiele szybsze niż rozwidlanie się wszędzie (29ms vs 734ms w mojej muzyce reż - oba „ciepłe” znaleziska)
Oli
9

Jest to o wiele łatwiejsze do rozwiązania z globowaniem niż z find.

$ cd ... # to the directory one level above the album/artist structure

$ echo */*/*.cover   # lists all the covers

$ printf "%s\n" */*/*.cover # lists all the covers, one per line

Załóżmy teraz, że w tej ładnej strukturze nie ma zbłąkanych plików. Bieżący katalog zawiera tylko podkatalogi wykonawców, a te zawierają tylko podkatalogi albumów. Następnie możemy zrobić coś takiego:

$ diff  <(for x in */*/cover.jpg; do echo "$(dirname "$x")" ; done) <(printf "%s\n" */*)

<(...)Składnia jest Bash podstawienie proces: to pozwala użyć polecenia zamiast argumentu plikowego. Pozwala traktować dane wyjściowe polecenia jako plik. Możemy więc uruchomić dwa programy i pobrać ich różnice bez zapisywania ich wyników w plikach tymczasowych. diffProgram myśli, że pracuje z dwoma plikami, ale w rzeczywistości jest to odczyt z dwóch rur.

Polecenie to powoduje wejście prawą rękę diff, printf "%s\n" */*tylko wymienia katalogi album. Polecenie po lewej stronie wykonuje iterację *.coverścieżek i wypisuje nazwy katalogów.

Testowe uruchomienie:

$ find .   # let's see what we have here
.
./a
./a/b
./foo
./foo/bar
./foo/baz
./foo/baz/cover.jpg

$ diff  <(for x in */*/cover.jpg; do echo "$(dirname "$x")" ; done) <(printf "%s\n" */*)
0a1,2
> a/b
> foo/bar

Aha, a/bi foo/barkatalogi nie mają cover.jpg.

Istnieją przypadki łamania narożników, takie jak to, że domyślnie *rozwija się do siebie, jeśli nie pasuje do niczego. Można to rozwiązać za pomocą Basha set -o nullglob.

Zaraz
źródło
Przepraszamy za spóźnioną odpowiedź. To ciekawy pomysł, ale: okładki mogą być w png i jpb i czy nie commbyłyby czystsze niż diff?
Oli
comm -3 <(printf "%s\n" */*/cover* | sed -r 's/\/[^\/]+$//' | sort -u) <(printf "%s\n" */*)wydaje się rozsądnym kompromisem bez diffpuchu. Jest jednak trochę wolniejszy niż moje podwójne znalezisko.
Oli
0
ls --color=never */*.txt | sed 's|/.*||' | sort -u -n > withtxt.txt
ls --color=never -d * | sort -u -n > all.txt
diff all.txt withtxt.txt

Pokaże wszystkie katalogi, w których nie ma plików txt.

Roel Van de Paar
źródło