як перевірити, чи каталог порожній


15

У мене є вимога, якщо я виконую скрипт ./123з аргументами порожнього шляху, скажімо /usr/share/linux-headers-3.16.0-34-generic/.tmp_versions(цей каталог порожній). Він повинен відображати "каталог порожній"

Мій код:

#!/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

У мене з'являються повідомлення про помилку, як-от, якщо я виконую ім'я скрипта ./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions(ця директорія порожня).

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

замість того, щоб відображати лише вихід "Каталог порожній", він показує наведений вище вихід

Висновок нижче повинен бути відображений, якщо я перебільшую сценарій правильними аргументами (я маю на увазі правильний шлях до каталогу). сказати./123 /usr/share

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

мій очікуваний вихід: ./123 /usr/src/linux-headers-3.16.0-34-generic/.tmp_versions

directory is empty.


хтось може будь-коли перевірити мій код один раз. Я відредагував це
Будда Срікант

Відповіді:


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

Наведене вище є тестом, сумісним з POSIX - і має бути дуже швидким. lsперерахує всі файли / dirs у каталозі, за винятком, .і ..кожен по рядку, і -qпобачить усі символи, що не надруковані (включаючи \newlines) у висновку з ?позначкою питання. Таким чином, якщо grepотримає навіть один символ у вхід, він поверне справжнє - інакше помилкове.

Щоб зробити це в оболонці 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-оболонка (яка не раніше відключило -fпокоління ilename) буде масив позиційно-параметр або буквальною рядку з подальшою командою вище, або до полів , породжені операторами Глоби в кінці кожного з них. Чи буде це робити, залежить від того, чи насправді кулі чимось відповідають. У деяких оболонках ви можете доручити нерозв’язному глобусу розширюватися до нуля - або взагалі нічого. Іноді це може бути корисним, але воно не є портативним і часто виникає з додатковими проблемами - такими, як встановлення спеціальних параметрів оболонки, а потім зняття їх.set"$@"set

Єдиний портативний засіб для обробки нульових аргументів передбачає або порожні, або нереєстровані змінні, або ~розширення в тильді. І остання, до речі, набагато безпечніша, ніж перша.

Над оболонкою випробовується лише будь-який з файлів на -eнаявність xistence, якщо жоден із зазначених трьох глобусів не відповідає більш ніж одному збігу. Таким чином, forцикл взагалі виконується лише протягом трьох або менших ітерацій, і лише у випадку порожнього каталогу або у випадку, коли одна чи більше шаблонів вирішується лише на один файл. forТакож breakS , якщо будь-який з грудок представляє фактичний файл - і , як я розташував кульки в порядку , швидше за все, НЕ менш ймовірно, він повинен в значній мірі вийти на першу ітерацію кожен раз.

У будь-якому випадку це повинно включати лише один системний stat()виклик - оболонку, і lsобидва повинні мати stat()лише запит до каталогу та перелік файлів, у яких міститься звіт про стоматологічні системи . Цьому протиставляється поведінка, findяка замість того, щоб stat()кожен файл, який ви могли з ним перелічити.


Можна підтвердити. Я використовую початковий приклад mikeserv у написаних сценаріях.
Отей

Це буде звітувати як порожні каталоги, які не існують або до яких ви не маєте доступу для читання. Для другого, якщо ви маєте доступ для читання, але не шукаєте, YMMV.
Стефан Шазелас

@ StéphaneChazelas - можливо, але такі звіти супроводжуватимуться повідомленнями про помилки, щоб сповістити користувача про таке.
mikeserv

1
Тільки у lsвипадку. кулі і [мовчать, коли не мають доступу. Наприклад, [ -e /var/spool/cron/crontabs/stephane ]мовчки повідомлять про помилкові, поки є файл з таким ім'ям.
Стефан Шазелас

Також зауважте, що ці рішення не є рівнозначними, якщо somedir є символьним посиланням на каталог (або не каталог).
Стефан Шазелас

6

З GNU або сучасними BSD findви можете:

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

(Передбачається , що $dirне виглядає як findпредикат ( !, (, -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

4

[-z $dir ]скаржиться, що [-zв більшості систем немає команди, яка викликається . Вам потрібні пробіли навколо дужок .

[ -z $dir ]буває істинним, якщо dirвін порожній, і хибний для більшості інших значень dir, але він недостовірний, наприклад, це правда, якщо значення dirє = -zабо -o -o -n -n. Завжди використовуйте подвійні лапки навколо підстановок команд (це стосується і решти сценарію).

[ -z "$dir" ]перевіряє, чи значення змінної dirпорожнє. Значення змінної - це рядок, який трапляється шляхом до каталогу. Це нічого не говорить вам про сам каталог.

Немає жодного оператора, який би перевіряв, чи каталог порожній, як і для звичайного файлу ( [ -s "$dir" ]справедливо для каталогу, навіть якщо він порожній). Простий спосіб перевірити, чи каталог пустий - це перелік його вмісту; якщо ви отримуєте порожній текст, каталог порожній.

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

У старих системах, яких немає ls -A, ви можете використовувати ls -a, але потім .і ..перераховані.

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

(Не відступайте рядок, який починається, ..оскільки рядок повинен містити лише новий рядок, а не новий рядок та додатковий пробіл.)


Ви можете пояснити, будь ласка, цю частину "$ (ls -A -" $ dir ")". що робить $ в та за межами дужок?
будда срікант


@Giles "$ (ls -A -" $ dir ")" не працює з помилкою кидка.
будда срікант

@buddhasreekanth У чому помилка? Копіювати Вставити. Ви не можете очікувати, що люди допоможуть вам, якщо ви просто скажете «не працюю», не пояснивши, що саме ви спостерігаєте.
Жил "ТАК - перестань бути злим"

@Giles - це помилка. Коли я виконував свій скрипт ./filestats його помилка кидання .. $ / filestats / home / sreekanth / testdir / aki / schatz мінімальний розмір файлу: 4096 / home / sreekanth / testdir / aki / schatz Максимальний розмір файлу: 4096 / home / sreekanth / testdir / aki / schatz середній розмір файлу: 4 каталог порожній. Замість того, щоб показувати "каталог порожній". Його відображення з мінімальним розміром, максимальним розміром та середнім розміром.
будда срікант

3

Ви дивитесь на атрибути самого каталогу.

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

Ви хочете порахувати, скільки файлів там:

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

Отже, тест для "каталогу порожній":

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

Подобається це:

$ 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

Це повідомить, що каталог порожній, якщо ви не маєте доступу до нього для читання або він не існує.
Стефан Шазелас

1

Моя відповідь tldr така:

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/..?*" ]
}

Він сумісний з POSIX, і це не так важливо, як правило, це швидше, ніж рішення, яке перераховує каталог та передає вихід на греп.

Використання:

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

Мені подобається відповідь https://unix.stackexchange.com/a/202276/160204 , яку я переписую як:

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

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

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

Ця функція не є стандартною POSIX і викликає передплату за допомогою $(). Спочатку я пояснюю цю просту функцію, щоб пізніше зрозуміти остаточне рішення (див. Відповідь tldr вище).

Пояснення:

Ліва сторона (LHS) порожня, коли не відбувається розширення, що є випадком, коли каталог порожній. Опція nullglob потрібна, оскільки в іншому випадку, коли немає відповідності, сам глобус є результатом розширення. (Якщо RHS відповідає глобусам LHS, коли каталог порожній, не працює через помилкові позитиви, які виникають, коли глобус LHS відповідає одному файлу, названому як сам глобус: *the glob відповідає збігу підрядків *у імені файлу. ) Вираз дужки {,.[^.],..?}охоплює приховані файли, але не ..або ..

Оскільки shopt -s nullglobвиконується всередині $()(підпакет), це не змінює nullglobпараметр поточної оболонки, що зазвичай є хорошою справою. З іншого боку, корисно встановити цю опцію в сценаріях, оскільки помилка схильна, щоб глобус повертав щось, коли немає відповідності. Отже, можна встановити параметр nullglob на початку сценарію, і він не знадобиться у функції. Пам'ятайте про це: ми хочемо рішення, яке працює з параметром nullglob.

Застереження:

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

shopt -s nullglobКоманда не є стандартом POSIX.

Він використовує підшару, створену компанією $(). Це не велика справа, але приємно, якщо ми можемо цього уникнути.

Про:

Не те, що це насправді має значення, але ця функція в чотири рази швидша за попередню, вимірюється кількістю часу процесора, проведеного в ядрі в процесі.

Інші рішення:

Ми можемо видалити нони POSIX shopt -s nullglobкоманди на LHS і помістіть рядок "$1/* $1/.[^.]* $1/..?*"в RHS і усунути окремо помилкові спрацьовування , які виникають , коли у нас є тільки файли з ім'ям '*', .[^.]*або ..?*в каталозі:

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

Без shopt -s nullglobкоманди тепер має сенс видалити нижню частину корпусу, але ми повинні бути обережними, оскільки ми хочемо уникнути розбиття слів і все ж дозволити розширення глобальної версії на LHS. Зокрема, цитування, щоб уникнути розбиття слів, не працює, оскільки це також запобігає розширенню глобальної сфери. Наше рішення - розглянути глобуси окремо:

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

У нас ще є розділення слів для окремих глобусів, але це нормально зараз, оскільки це призведе до помилки лише тоді, коли каталог не буде порожнім. Ми додали 2> / dev / null, щоб відкинути повідомлення про помилку, коли на LHS є багато файлів, що відповідають даному глобусу.

Ми пам'ятаємо, що ми хочемо рішення, яке також працює з параметром nullglob. Вищевказане рішення не відповідає параметру nullglob, оскільки коли каталог порожній, LHS також порожній. На щастя, ніколи не сказано, що каталог порожній, коли його немає. Лише не можна сказати, що вона порожня, коли є. Отже, ми можемо керувати параметром nullglob окремо. Ми не можемо просто додати справи [ "$1/"* = "" ]тощо, тому що вони розширяться як [ = "" ]і т.д., які є синтаксично неправильними. Отже, ми використовуємо [ "$1/"* "" = "" ]натомість тощо. Ми знову повинні розглянути три випадки *, ..?*і .[^.]*відповідно приховані файли, але не .та... Вони не завадять, якщо у нас немає опції nullglob, оскільки вони також ніколи не кажуть, що вона порожня, коли її немає. Отже, остаточне запропоноване рішення:

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/..?*" ]
}

Охорона безпеки:

Створіть два файли rmта xв порожньому каталозі та виконайте *підказку. Глобус *розшириться до, rm xі це буде виконано для видалення x. Це не є проблемою безпеки, оскільки в нашій функції глобуси розташовані там, де розширення розглядаються не як команди, а як аргументи, як у for f in *.


$(set -f; echo "$1"/*)видається досить складним способом написання "$1/*". І це не збігатиметься з прихованими каталогами чи файлами.
муру

Що я мав на увазі, це те, що в ньому немає розширення імені файлів "$1/*"(врахуйте, що котирування включають *), тож, і, set -fі додаткова оболонка для цього не потрібна .
муру

Він відповідає на інше запитання: чи містить каталог не приховані файли чи каталоги. Мені про це шкода. Я буду радий видалити його.
Dominic108

1
Є способи , щоб відповідати приховані файли теж (див ускладнених Glob в відповідь mikeserv в : ./* ./.[!.]* ./..?*). Можливо, ви можете включити це для завершення функції.
муру

Ах! Я перегляну це і дізнаюся, і, можливо, ми отримаємо повну відповідь на основі розширення глобалізації!
Dominic108

0

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

Тоді

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

Це працює, тому що

du -s D

має вихід

size_of_D   D

awk видаляє D, а якщо розмір 0 - каталог порожній.


-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

2
Привіт і ласкаво просимо на сайт. Будь ласка, не публікуйте відповіді, які є просто кодом і не пояснюють. Прокоментуйте код або поясніть іншим чином, що це ви робите. До речі, немає ніяких причин використовувати echo, все що вам потрібно list="$somedir/*". Також це хитро і схильно до помилок. Ви вже не згадували, що ОП повинна наводити шлях цільового реєстру, включаючи кінцеву косу рису, наприклад.
terdon
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.