Jawaban tldr saya adalah:
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/..?*" ]
}
Ini sesuai dengan POSIX dan, tidak terlalu penting, biasanya lebih cepat daripada solusi yang mencantumkan direktori dan menyalurkan output ke grep.
Pemakaian:
if emptydir adir
then
echo "nothing found"
else
echo "not empty"
fi
Saya suka jawabannya https://unix.stackexchange.com/a/202276/160204 , yang saya tulis ulang sebagai:
function emptydir {
! { ls -1qA "./$1/" | grep -q . ; }
}
Ini daftar direktori dan pipa hasilnya ke grep. Sebagai gantinya, saya mengusulkan fungsi sederhana yang didasarkan pada ekspansi dan perbandingan glob.
function emptydir {
[ "$(shopt -s nullglob; echo "$1"/{,.[^.],..?}*)" = "" ]
}
Fungsi ini bukan POSIX standar dan memanggil subkulit dengan $()
. Saya menjelaskan fungsi sederhana ini terlebih dahulu sehingga kita dapat lebih memahami solusi akhir (lihat jawaban tldr di atas) nanti.
Penjelasan:
Sisi kiri (LHS) kosong ketika tidak ada ekspansi, yang merupakan kasus ketika direktori kosong. Opsi nullglob diperlukan karena jika tidak ada kecocokan, glob itu sendiri adalah hasil dari ekspansi. (Memiliki RHS cocok dengan gumpalan LHS ketika direktori kosong tidak bekerja karena positif palsu yang terjadi ketika LHS glob cocok dengan satu file bernama glob itu sendiri: the *
in the glob cocok dengan substring *
pada nama file. ) Ekspresi penjepit {,.[^.],..?}
mencakup file tersembunyi, tetapi tidak ..
atau .
.
Karena shopt -s nullglob
dijalankan di dalam $()
(subkulit), itu tidak mengubah nullglob
opsi shell saat ini, yang biasanya merupakan hal yang baik. Di sisi lain, itu ide yang baik untuk mengatur opsi ini dalam skrip, karena itu adalah kesalahan untuk memiliki gumpalan mengembalikan sesuatu ketika tidak ada kecocokan. Jadi, seseorang dapat mengatur opsi nullglob di awal skrip dan itu tidak akan diperlukan dalam fungsinya. Ingatlah ini: kami menginginkan solusi yang berfungsi dengan opsi nullglob.
Peringatan:
Jika kita tidak memiliki akses baca ke direktori, fungsinya melaporkan sama seperti jika ada direktori kosong. Ini berlaku juga untuk fungsi yang mencantumkan direktori dan menerima output.
The shopt -s nullglob
perintah tidak standar POSIX.
Ini menggunakan subkulit yang dibuat oleh $()
. Ini bukan masalah besar, tapi menyenangkan jika kita bisa menghindarinya.
Pro:
Bukan berarti itu penting, tetapi fungsi ini empat kali lebih cepat dari yang sebelumnya, diukur dengan jumlah waktu CPU yang dihabiskan di kernel dalam proses.
Solusi lain:
Kita dapat menghapus perintah non POSIX shopt -s nullglob
pada LHS dan meletakkan string "$1/* $1/.[^.]* $1/..?*"
di RHS dan menghilangkan secara terpisah positif palsu yang terjadi ketika kita hanya memiliki file bernama '*'
, .[^.]*
atau ..?*
di direktori:
function emptydir {
[ "$(echo "$1"/{,.[^.],..?}*)" = "$1/* $1/.[^.]* $1/..?*" ] &&
[ ! -e "$1/*" ] && [ ! -e "$1/.[^.]*" ] && [ ! -e "$1/..?*" ]
}
Tanpa shopt -s nullglob
perintah, sekarang masuk akal untuk menghapus subkulit, tetapi kita harus berhati-hati karena kita ingin menghindari pemisahan kata dan belum mengizinkan ekspansi global pada LHS. Secara khusus mengutip untuk menghindari pemisahan kata tidak berfungsi, karena itu juga mencegah ekspansi gumpal. Solusi kami adalah mempertimbangkan gumpalan secara terpisah:
function emptydir {
[ "$1/"* = "$1/*" ] 2> /dev/null && [ ! -e "$1/*" ] &&
[ "$1/".[^.]* = "$1/.[^.]*" ] 2> /dev/null && [ ! -e "$1/.[^.]*" ] &&
[ "$1/"..?* = "$1/..?*" ] 2> /dev/null && [ ! -e "$1/..?*" ]
}
Kami masih memiliki pemisahan kata untuk glob individu, tetapi tidak apa-apa sekarang, karena itu hanya akan menghasilkan kesalahan ketika direktori tidak kosong. Kami menambahkan 2> / dev / null, untuk membuang pesan kesalahan ketika ada banyak file yang cocok dengan bola yang diberikan pada LHS.
Kami ingat bahwa kami menginginkan solusi yang bekerja dengan opsi nullglob juga. Solusi di atas gagal dengan opsi nullglob, karena ketika direktori kosong, LHS juga kosong. Untungnya, tidak pernah dikatakan bahwa direktori tersebut kosong ketika tidak. Itu hanya gagal untuk mengatakan bahwa itu kosong ketika itu. Jadi, kita dapat mengelola opsi nullglob secara terpisah. Kami tidak dapat dengan mudah menambahkan kasus [ "$1/"* = "" ]
dll. Karena ini akan berkembang sebagai [ = "" ]
, dll. Yang secara sintaksis salah. Jadi, kami menggunakan [ "$1/"* "" = "" ]
dll. Kita lagi harus mempertimbangkan tiga kasus *
, ..?*
dan .[^.]*
untuk mencocokkan file yang tersembunyi, tetapi tidak .
dan..
. Ini tidak akan mengganggu jika kita tidak memiliki opsi nullglob, karena mereka juga tidak pernah mengatakan bahwa itu kosong ketika tidak ada. Jadi, solusi akhir yang diusulkan adalah:
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/..?*" ]
}
Masalah keamanan:
Buat dua file rm
dan x
dalam direktori kosong dan jalankan *
pada prompt. Glob *
akan meluas ke rm x
dan ini akan dieksekusi untuk menghapus x
. Ini bukan masalah keamanan, karena dalam fungsi kami, gumpalan terletak di mana ekspansi tidak dilihat sebagai perintah, tetapi sebagai argumen, seperti di for f in *
.