jak sprawdzić katalog jest pusty

15

Mam wymaganie, jeśli wykonam skrypt ./123z argumentami pustej ścieżki, powiedzmy /usr/share/linux-headers-3.16.0-34-generic/.tmp_versions(ten katalog jest pusty). Powinien zostać wyświetlony komunikat „katalog jest pusty”

Mój kod to:

#!/bin/bash
dir="$1"

if [ $# -ne 1 ]
then
    echo "please pass arguments" 
exit
fi

if [ -e $dir ]
then
printf "minimum file size: %s\n\t%s\n" \
 $(du $dir -hab | sort -n -r | tail -1)

printf "maximum file size: %s\n\t%s\n" \
 $(du $dir -ab | sort -n | tail -1)

printf "average file size: %s"
du $dir -sk | awk '{s+=$1}END{print s/NR}'
else
   echo " directory doesn't exists"
fi

if [ -d "ls -A $dir" ]
 then
    echo " directory is  empty"
fi

Mam komunikat o błędzie, np. Jeśli wykonam nazwę skryptu ./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions(ten katalog jest pusty).

minimum file size: 4096
    /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions
maximum file size: 4096
    /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions
average file size: 4

zamiast pokazywać dane wyjściowe „katalog jest pusty” pokazuje powyższe dane wyjściowe

Poniższe dane wyjściowe muszą zostać wyświetlone, jeśli skasuję skrypt za pomocą poprawnych argumentów (mam na myśli prawidłową ścieżkę katalogu). mówić./123 /usr/share

minimum file size: 196
        /usr/share
    maximum file size: 14096
        /usr/share
    average file size: 4000

moja oczekiwana wydajność to: ./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions

directory is empty.
buddha sreekanth
źródło
4
Zobacz mywiki.wooledge.org/BashFAQ/004
Valentin Bajrami
czy ktoś może raz sprawdzić mój kod? Ja to zredagowałem
sreekanth Buddy
Powiązane: superuser.com/questions/352289/…
AlikElzin-kilaka

Odpowiedzi:

20
if    ls -1qA ./somedir/ | grep -q .
then  ! echo somedir is not empty
else  echo somedir is empty
fi

Powyższe jest testem zgodnym z POSIX - i powinno być bardzo szybkie. lswyświetli listę wszystkich plików / katalogów z wyjątkiem jednego .i ..każdego w wierszu oraz -quoteuje wszystkie znaki niedrukowalne (w tym \newline) na wyjściu ze ?znakiem zapytania. W ten sposób, jeśli grepodbierze choćby jeden znak wejściowy, zwróci true - w przeciwnym razie false.

Aby to zrobić tylko w powłoce POSIX:

cd  ./somedir/ || exit
set ./* ./.[!.]* ./..?*
if   [ -n "$4" ] ||
     for e do 
         [ -L "$e" ] ||
         [ -e "$e" ] && break
     done
then ! echo somedir is not empty
else   echo somedir is empty
fi
cd "$OLDPWD"

POSIX powłoki (która nie wcześniej wyłączone -fgeneracji ilename) będzie Tablica położenia parametr się albo do tekstowymi następnie przez polecenia powyżej, albo do pól wytwarzanych przez operatorów glob na końcu każdego z nich. To, czy tak się stanie, zależy od tego, czy globusy faktycznie coś pasują. W niektórych powłokach możesz poinstruować nierozdzielającą glob, aby rozwinął się do zera - albo wcale. Czasami może to być korzystne, ale nie jest przenośne i często wiąże się z dodatkowymi problemami - takimi jak konieczność ustawienia specjalnych opcji powłoki, a następnie ich rozbrojenia.set"$@"set

Jedynym przenośnym sposobem obsługi argumentów o wartości zerowej są puste lub nieuzbrojone zmienne lub ~rozszerzenia tyldy. A ta druga, nawiasem mówiąc, jest znacznie bezpieczniejsza niż pierwsza.

Nad powłoką testuje tylko dowolny plik pod kątem -eistnienia, jeśli żadna z trzech określonych globów nie rozwiązuje więcej niż jednego dopasowania. forPętla jest więc w ogóle uruchamiana tylko dla trzech lub mniej iteracji i tylko w przypadku pustego katalogu lub w przypadku, gdy jeden lub więcej wzorców jest rozwiązywanych tylko do jednego pliku. forRównież breakS Jeżeli którykolwiek z globs stanowią rzeczywisty plik - i jak ja urządzam globs w kolejności od najbardziej do najmniej prawdopodobne prawdopodobne, powinna ona prawie rzucić na pierwszej iteracji za każdym razem.

Tak czy inaczej, powinno to dotyczyć tylko jednego stat()wywołania systemowego - powłoki i lsoba powinny tylko przeglądać stat()katalog i wyświetlać listę plików, które raportuje dentries . Kontrastuje to z zachowaniem, findktóre zamiast tego stat()zawierałby każdy plik, który można z nim wymienić.

mikeserv
źródło
Potwierdzam Pierwszego przykładu mikeserv używam w skryptach, które napisałem.
Otheus
Zgłaszałoby się to jako puste katalogi, które nie istnieją lub dla których nie masz dostępu do odczytu. Po drugie, jeśli masz dostęp do odczytu, ale nie masz dostępu do wyszukiwania, YMMV.
Stéphane Chazelas,
@ StéphaneChazelas - być może, ale do takich raportów będą dołączane komunikaty o błędach informujące o tym użytkownika.
mikeserv
1
Tylko w tej lssprawie. globusy i [milczą, gdy nie mają dostępu. Na przykład [ -e /var/spool/cron/crontabs/stephane ]po cichu zgłasza wartość fałsz, podczas gdy istnieje plik o tej nazwie.
Stéphane Chazelas,
Zauważ też, że te rozwiązania nie są równoważne, jeśli somedir jest dowiązaniem symbolicznym do katalogu (lub nie katalogu).
Stéphane Chazelas
6

Dzięki GNU lub nowoczesnym BSD findmożesz:

if find -- "$dir" -prune -type d -empty | grep -q .; then
  printf '%s\n' "$dir is an empty directory"
else
  printf >&2 '%s\n' "$dir is not empty, or is not a directory" \
                    "or is not readable or searchable in which case" \
                    "you should have seen an error message from find above."
fi

(zakłada $dirnie wygląda jak findorzecznika ( !, (, -name...).

POSIXly:

if [ -d "$dir" ] && files=$(ls -qAL -- "$dir") && [ -z "$files" ]; then
  printf '%s\n' "$dir is an empty directory"
else
  printf >&2 '%s\n' "$dir is not empty, or is not a directory" \
                    "or is not readable or searchable in which case" \
                    "you should have seen an error message from ls above."
fi
Stéphane Chazelas
źródło
4

[-z $dir ]narzeka, że ​​w [-zwiększości systemów nie ma polecenia . Potrzebujesz odstępów wokół nawiasów .

[ -z $dir ]zdarza się prawda, jeśli dirjest pusta i jest fałszywa dla większości innych wartości dir, ale jest niewiarygodna, na przykład jest prawdą, jeśli wartość dirto = -zlub -o -o -n -n. Zawsze używaj podwójnych cudzysłowów wokół podstawień poleceń (dotyczy to również reszty skryptu).

[ -z "$dir" ]sprawdza, czy wartość zmiennej dirjest pusta. Wartością zmiennej jest ciąg znaków, którym jest ścieżka do katalogu. To nic nie mówi o samym katalogu.

Nie ma operatora sprawdzającego, czy katalog jest pusty, tak jak w przypadku zwykłego pliku ( [ -s "$dir" ]dotyczy to katalogu, nawet jeśli jest pusty). Prostym sposobem sprawdzenia, czy katalog jest pusty, jest wyświetlenie jego zawartości; jeśli otrzymasz pusty tekst, katalog jest pusty.

if [ -z "$(ls -A -- "$dir")" ]; then 

W starszych systemach, które nie mają ls -A, możesz użyć ls -a, ale wtedy .i ..są na liście.

if [ -z "$(LC_ALL=C ls -a -- "$dir")" = ".
.." ]; then 

(Nie wcinaj wiersza, który zaczyna się ..od, ponieważ ciąg musi zawierać tylko nowy wiersz, a nie nowy wiersz i dodatkowe spacje.)

Gilles „SO- przestań być zły”
źródło
czy mógłbyś wyjaśnić tę część „$ (ls -A -„ $ dir ”)”. co robi $ w nawiasach i poza nimi?
buddha sreekanth
@buddhasreekanth $(…)jest polecenie podstawienie
Gilles „SO oporowy jest zła”
@Giles „$ (ls -A -„ $ dir ”)” nie działa błąd zgłaszania.
buddha sreekanth
@buddhasreekanth Jaki jest błąd? Kopiuj wklej. Nie możesz oczekiwać, że ludzie ci pomogą, jeśli powiesz „nie działa” bez wyjaśnienia dokładnie tego, co obserwujesz.
Gilles „SO- przestań być zły”
@Giles to jest błąd. Podczas wykonywania skryptu ./filestats wystąpił błąd wyrzucania .. $ / filestats / home / sreekanth / testdir / aki / schatz minimalny rozmiar pliku: 4096 / home / sreekanth / testdir / aki / schatz maksymalny rozmiar pliku: 4096 / home / sreekanth / testdir / aki / schatz średni rozmiar pliku: 4 katalog jest pusty. Zamiast pokazywać „katalog jest pusty”. Wyświetlanie z minimalnym rozmiarem, maksymalnym rozmiarem i średnim rozmiarem.
buddha sreekanth
3

Patrzysz na atrybuty samego katalogu.

$ mkdir /tmp/foo
$ ls -ld /tmp/foo
drwxr-xr-x 2 jackman jackman 4096 May  8 11:32 /tmp/foo
# ...........................^^^^

Chcesz policzyć, ile jest tam plików:

$ dir=/tmp/foo
$ shopt -s nullglob
$ files=( "$dir"/* "$dir"/.* )
$ echo ${#files[@]}
2
$ printf "%s\n" "${files[@]}"
/tmp/foo/.
/tmp/foo/..

Tak więc testem „katalog jest pusty” jest:

function is_empty {
    local dir="$1"
    shopt -s nullglob
    local files=( "$dir"/* "$dir"/.* )
    [[ ${#files[@]} -eq 2 ]]
}

Lubię to:

$ if is_empty /tmp/foo; then echo "it's empty"; else echo "not empty"; fi
it's empty
$ touch /tmp/foo/afile
$ if is_empty /tmp/foo; then echo "it's empty"; else echo "not empty"; fi
not empty
Glenn Jackman
źródło
To zgłosi, że katalog jest pusty, jeśli nie masz do niego dostępu do odczytu lub nie istnieje.
Stéphane Chazelas
1

Moja tldr odpowiedź brzmi:

function emptydir {
 [ "$1/"* "" = "" ]  2> /dev/null &&
 [ "$1/"..?* "" = "" ]  2> /dev/null &&
 [ "$1/".[^.]* "" = "" ]  2> /dev/null ||
 [ "$1/"* = "$1/*" ]  2> /dev/null && [ ! -e "$1/*" ] &&
 [ "$1/".[^.]* = "$1/.[^.]*" ]  2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
 [ "$1/"..?* = "$1/..?*" ]  2> /dev/null && [ ! -e "$1/..?*" ]
}

Jest zgodny z POSIX i, choć nie ma to większego znaczenia, zwykle jest szybszy niż rozwiązanie, które wyświetla katalog i przesyła dane wyjściowe do grep.

Stosowanie:

if emptydir adir
then
  echo "nothing found" 
else 
  echo "not empty" 
fi

Podoba mi się odpowiedź https://unix.stackexchange.com/a/202276/160204 , którą przepisuję jako:

function emptydir {
  ! { ls -1qA "./$1/" | grep -q . ; }
}

Wyświetla katalog i potokuje wynik do grep. Zamiast tego proponuję prostą funkcję opartą na globalnej ekspansji i porównaniu.

function emptydir {   
 [ "$(shopt -s nullglob; echo "$1"/{,.[^.],..?}*)" = "" ]
}

Ta funkcja nie jest standardowym POSIX-em i wywołuje podpowłokę z $(). Najpierw wyjaśniam tę prostą funkcję, aby później lepiej zrozumieć ostateczne rozwiązanie (patrz odpowiedź tldr powyżej).

Wyjaśnienie:

Lewa strona (LHS) jest pusta, gdy nie ma rozwinięcia, co ma miejsce, gdy katalog jest pusty. Opcja nullglob jest wymagana, ponieważ w przeciwnym razie, gdy nie ma dopasowania, glob jest wynikiem rozszerzenia. (Gdy RHS pasuje do globów LHS, gdy katalog jest pusty, nie działa z powodu fałszywych trafień, które występują, gdy glob LHS pasuje do pojedynczego pliku o nazwie jako glob: *glob w dopasowuje podłańcuch *w nazwie pliku. ) Wyrażenie nawiasów {,.[^.],..?}obejmuje ukryte pliki, ale nie ..lub ..

Ponieważ shopt -s nullglobjest wykonywany wewnątrz $()(podpowłoki), nie zmienia nullglobopcji bieżącej powłoki, co zwykle jest dobrą rzeczą. Z drugiej strony dobrym pomysłem jest ustawienie tej opcji w skryptach, ponieważ podatność na błędy powoduje, że glob zwraca coś, gdy nie ma dopasowania. Można więc ustawić opcję nullglob na początku skryptu i nie będzie ona potrzebna w funkcji. Pamiętajmy o tym: chcemy rozwiązania, które działa z opcją nullglob.

Ostrzeżenia:

Jeśli nie mamy dostępu do odczytu katalogu, funkcja zgłasza to samo, jakby był pusty katalog. Dotyczy to również funkcji, która wyświetla katalog i grep wyjście.

shopt -s nullglobKomenda nie jest standardem POSIX.

Używa podpowłoki utworzonej przez $(). To nie jest wielka sprawa, ale fajnie, jeśli możemy tego uniknąć.

Zawodowiec:

Nie, żeby to naprawdę miało znaczenie, ale ta funkcja jest czterokrotnie szybsza niż poprzednia, mierzona ilością czasu procesora spędzonego w jądrze w procesie.

Inne rozwiązania:

Możemy usunąć non POSIX shopt -s nullglobpolecenie na LHS i umieścić napis "$1/* $1/.[^.]* $1/..?*"w RHS i wyeliminować oddzielnie fałszywych alarmów, które występują, gdy mamy tylko pliki o nazwie '*', .[^.]*lub ..?*w katalogu:

function emptydir {
 [ "$(echo "$1"/{,.[^.],..?}*)" = "$1/* $1/.[^.]* $1/..?*" ] &&
 [ ! -e "$1/*" ] && [ ! -e "$1/.[^.]*" ] && [ ! -e "$1/..?*" ]
}

Bez shopt -s nullglobpolecenia sensowne jest teraz usunięcie podpowłoki, ale musimy zachować ostrożność, ponieważ chcemy uniknąć podziału słów, a jednocześnie umożliwić globalną ekspansję w LHS. W szczególności cytowanie w celu uniknięcia dzielenia słów nie działa, ponieważ zapobiega także globalnej ekspansji. Nasze rozwiązanie polega na osobnym rozpatrzeniu globów:

function emptydir {
 [ "$1/"* = "$1/*" ] 2> /dev/null && [ ! -e "$1/*" ] &&
 [ "$1/".[^.]* = "$1/.[^.]*" ] 2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
 [ "$1/"..?* = "$1/..?*" ] 2> /dev/null && [ ! -e "$1/..?*" ]
}

Wciąż mamy dzielenie słów dla poszczególnych globów, ale teraz jest w porządku, ponieważ spowoduje to błąd tylko wtedy, gdy katalog nie będzie pusty. Dodaliśmy 2> / dev / null, aby odrzucić komunikat o błędzie, gdy na LHS jest wiele plików pasujących do podanego globu.

Przypominamy, że chcemy rozwiązania, które będzie działać również z opcją nullglob. Powyższe rozwiązanie kończy się niepowodzeniem w przypadku opcji nullglob, ponieważ gdy katalog jest pusty, LHS również jest pusty. Na szczęście nigdy nie mówi się, że katalog jest pusty, gdy nie jest. Nie mówi tylko, że jest pusty, kiedy jest. Możemy więc zarządzać opcją nullglob osobno. Nie możemy po prostu dodać przypadków [ "$1/"* = "" ]itp., Ponieważ będą się one rozwijać jako [ = "" ]itd., Które są niepoprawne pod względem składniowym. [ "$1/"* "" = "" ]Zamiast tego używamy itp. Znów trzeba rozważyć trzy przypadki *, ..?*i .[^.]*dopasować ukryte pliki, ale nie .i... Nie będą przeszkadzać, jeśli nie będziemy mieli opcji nullglob, ponieważ nigdy też nie mówią, że jest pusta, gdy nie jest. Tak więc końcowe proponowane rozwiązanie to:

function emptydir {
 [ "$1/"* "" = "" ]  2> /dev/null &&
 [ "$1/"..?* "" = "" ]  2> /dev/null &&
 [ "$1/".[^.]* "" = "" ]  2> /dev/null ||
 [ "$1/"* = "$1/*" ]  2> /dev/null && [ ! -e "$1/*" ] &&
 [ "$1/".[^.]* = "$1/.[^.]*" ]  2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
 [ "$1/"..?* = "$1/..?*" ]  2> /dev/null && [ ! -e "$1/..?*" ]
}

Zagadnienie bezpieczeństwa:

Utwórz dwa pliki rmi xpustego katalogu i uruchomić *na monit. Glob *rozszerzy się rm xi zostanie on wykonany w celu usunięcia x. Nie dotyczy to bezpieczeństwa, ponieważ w naszej funkcji globusy znajdują się tam, gdzie rozszerzenia nie są postrzegane jako polecenia, ale jako argumenty, tak jak w for f in *.

Dominic108
źródło
$(set -f; echo "$1"/*)wydaje się dość skomplikowanym sposobem pisania "$1/*". I to nie pasuje do ukrytych katalogów lub plików.
muru
Chodziło mi o to, że nie ma rozszerzenie nazwy pliku w "$1/*"(nota cytaty należą *), więc set -fi powłoki w tle są zbędne na które w szczególności.
muru
Odpowiada na inne pytanie: czy katalog zawiera nie ukryte pliki lub katalogi. Przepraszam za to. Z przyjemnością go usunę.
Dominic108,
1
Istnieją sposoby, aby dopasować ukryte pliki zbyt (patrz skomplikowanej glob w odpowiedzi mikeserv za : ./* ./.[!.]* ./..?*). Być może możesz to włączyć, aby funkcja była kompletna.
muru
Ach! Spojrzę na to i nauczę się, a być może będziemy mieć pełną odpowiedź opartą na ekspansji globbing!
Dominic108,
0

Oto kolejny prosty sposób na zrobienie tego. Załóżmy, że D jest pełną nazwą ścieżki katalogu, który chcesz przetestować pod kątem pustki.

Następnie

if [[ $( du -s  D |awk ' {print $1}') = 0 ]]
then
      echo D is empty
fi

Działa to, ponieważ

du -s D

ma jako wynik

size_of_D   D

awk usuwa D, a jeśli rozmiar wynosi 0, katalog jest pusty.

Richard Gostanian
źródło
-1
#/bin/sh

somedir=somedir
list="`echo "$somedir"/*`"

test "$list" = "$somedir/*" && echo "$somedir is empty"

# dot prefixed entries would be skipped though, you have to explicitly check them
użytkownik137815
źródło
2
Witam i witam na stronie. Proszę nie publikować odpowiedzi, które są tylko kodem i bez wyjaśnień. Skomentuj kod lub w inny sposób wyjaśnij, co robisz. Nawiasem mówiąc, nie ma powodu do korzystania echo, wszystko czego potrzebujesz to list="$somedir/*". Jest to również trudne i podatne na błędy. Nie wspominałeś, że OP powinien podać ścieżkę docelowego katalogu, w tym na przykład ukośnik końcowy.
terdon